Skip to content

Commit 7ccf648

Browse files
authored
Merge pull request #26 from ONS-Innovation/KEH-609-admin-access
KEH-609 | Admin Access
2 parents 9d61c5a + 929d23e commit 7ccf648

4 files changed

Lines changed: 65 additions & 17 deletions

File tree

aws_lambda_script/app/resources.py

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,29 +33,66 @@
3333
logging.basicConfig(level=logging.INFO)
3434
logger = logging.getLogger(__name__)
3535

36+
def get_user_attributes(args):
37+
"""Get user attributes from Cognito token.
3638
37-
# Function to get the user email from the token
38-
def get_user_email(args):
39+
Args:
40+
args (dict): Request arguments containing the Authorization header.
41+
42+
Returns:
43+
dict: User attributes extracted from the token.
44+
45+
Raises:
46+
Exception: If token verification fails.
47+
"""
3948
token = args["Authorization"]
4049
try:
4150
user_attributes = verify_cognito_token(token)
42-
owner_email = user_attributes["email"]
43-
if not owner_email:
44-
logger.error("No email found in user attributes")
45-
abort(401, description="Not authorized")
46-
return owner_email
51+
return user_attributes
4752
except Exception as error:
4853
logger.exception("Error verifying token: %s", error)
4954
abort(401, description="Not authorized")
5055

56+
def get_user_information() -> dict:
57+
"""A function to collect user information from a given Cognito Token.
58+
59+
Returns:
60+
dict: A dictionary containing the user's email and groups.
61+
62+
Raises:
63+
abort: If the email is not found in user attributes.
64+
"""
65+
user_attributes = get_user_attributes(parser.parse_args())
66+
67+
owner_email = user_attributes.get("email", "")
68+
user_groups = user_attributes.get("cognito:groups", [])
69+
70+
if not owner_email:
71+
logger.error("No email found in user attributes")
72+
abort(401, description="Not authorized")
73+
74+
return {"email": owner_email, "groups": user_groups}
75+
76+
def is_auth_user_in_admin_group(user_groups: list) -> bool:
77+
"""Check if the authenticated user is in the admin group.
78+
79+
Args:
80+
user_groups (list): List of groups the user belongs to.
81+
82+
Returns:
83+
bool: True if user is in admin group, False otherwise.
84+
"""
85+
return "Admin" in user_groups
5186

5287
# Route to return the user email from the token in authorization header
5388
@ns.route("/user")
5489
class User(Resource):
5590
@ns.doc(responses={200: "Success", 401: "Authorization is required"})
5691
def get(self):
57-
owner_email = get_user_email(parser.parse_args())
58-
return {"email": owner_email}, 200
92+
user_info = get_user_information()
93+
owner_email = user_info["email"]
94+
user_groups = user_info["groups"]
95+
return {"email": owner_email, "groups": user_groups}, 200
5996

6097

6198
# Route to return all projects with optional filters
@@ -339,7 +376,6 @@ class Projects(Resource):
339376
@ns.doc(responses={200: "Success", 401: "Authorization is required"})
340377
# @ns.marshal_list_with(project_model)
341378
def get(self):
342-
owner_email = get_user_email(parser.parse_args())
343379
data = read_data("new_project_data.json")
344380
user_projects = [proj for proj in data["projects"]]
345381
return user_projects, 200
@@ -358,7 +394,8 @@ def get(self):
358394
},
359395
)
360396
def post(self):
361-
owner_email = get_user_email(parser.parse_args())
397+
user_info = get_user_information()
398+
owner_email = user_info["email"]
362399

363400
# Check that required fields are present in the JSON payload
364401
new_project = ns.payload
@@ -438,8 +475,6 @@ class ProjectDetail(Resource):
438475
# the name and the user email in the first user item in the user list
439476
@ns.marshal_with(project_model)
440477
def get(self, project_name):
441-
owner_email = get_user_email(parser.parse_args())
442-
443478
# Sanitize project_name by replacing '%20' with spaces
444479
project_name = (
445480
project_name.replace("%20", " ").replace("\r\n", "").replace("\n", "")
@@ -474,7 +509,10 @@ def get(self, project_name):
474509
},
475510
)
476511
def put(self, project_name):
477-
owner_email = get_user_email(parser.parse_args())
512+
user_info = get_user_information()
513+
owner_email = user_info["email"]
514+
user_groups = user_info["groups"]
515+
478516
project_name = (
479517
project_name.replace("%20", " ").replace("\r\n", "").replace("\n", "")
480518
)
@@ -494,17 +532,18 @@ def put(self, project_name):
494532
if key not in environments or not isinstance(environments[key], bool):
495533
abort(400, description=f"Invalid environments data: '{key}' must be a boolean")
496534

497-
498535
# Ensure the email is set to owner_email
499536

500537
data = read_data("new_project_data.json")
501538

539+
logger.info(f"Authenticated user is in admin group: {is_auth_user_in_admin_group(user_groups)}")
540+
502541
project = next(
503542
(
504543
proj
505544
for proj in data["projects"]
506545
if proj["details"][0]["name"] == project_name
507-
and any(user["email"] == owner_email for user in proj["user"])
546+
and (any(user["email"] == owner_email for user in proj["user"]) or is_auth_user_in_admin_group(user_groups))
508547
),
509548
None,
510549
)

terraform/api_gateway/main.tf

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,14 @@ resource "aws_api_gateway_deployment" "main" {
301301
lifecycle {
302302
create_before_destroy = true
303303
}
304+
305+
// This block will cause a redeployment of the API Gateway whenever the main.tf file changes
306+
// Since the API routes are defined in this file, any changes to the routes will trigger a redeployment
307+
triggers = {
308+
redeployment = sha1(jsonencode([
309+
file("main.tf")
310+
]))
311+
}
304312
}
305313

306314
# API Gateway Stage

terraform/authentication/main.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ module "cognito" {
2323
token_validity_values = var.token_validity_values
2424
token_validity_units = var.token_validity_units
2525
callback_urls = var.callback_urls
26+
user_groups = {"Admin": "Admin User Group. Adds an admin user group to the user pool so users can edit all projects."}
2627
}

terraform/lambda/main.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ resource "aws_lambda_function" "tech_audit_lambda" {
164164
variables = {
165165
TECH_AUDIT_DATA_BUCKET = data.terraform_remote_state.storage.outputs.tech_audit_data_bucket_name
166166
TECH_AUDIT_SECRET_MANAGER = data.terraform_remote_state.secrets.outputs.secret_name
167-
AWS_COGNITO_TOKEN_URL = "https://${var.service_subdomain}-${var.domain}.auth.eu-west-2.amazoncognito.com/oauth2/token"
167+
AWS_COGNITO_TOKEN_URL = "https://${var.domain}-${var.service_subdomain}.auth.eu-west-2.amazoncognito.com/oauth2/token"
168168
}
169169
}
170170

0 commit comments

Comments
 (0)