Skip to content

Commit d4d799e

Browse files
authored
Merge pull request #97 from browserstack/fix-pending-resolve
feat: add resolve pendingFailures
2 parents a637a3c + 3a679c8 commit d4d799e

File tree

11 files changed

+203
-65
lines changed

11 files changed

+203
-65
lines changed

Access/accessrequest_helper.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@
7878
- {request_id} - Approver: {approver}"
7979
ERROR_DECLINING_REQUEST_LOG_MSG = "Error in Decline of request {request_id}. \
8080
Error:{error} .Please contact admin."
81+
ERROR_MARKING_RESOLVE_FAIL_LOG_MSG = "Error in resolving request {request_id}. \
82+
Error:{error} ."
8183

8284

8385
def get_request_access(request):
@@ -896,6 +898,22 @@ def decline_group_access(request, request_id, reason):
896898
)
897899

898900

901+
def run_ignore_failure_task(auth_user, access_mapping, request_id, selector):
902+
try:
903+
if selector == "decline":
904+
access_mapping.decline_access()
905+
elif selector == "approve":
906+
access_mapping.approve_access()
907+
notifications.send_mail_for_request_resolve(auth_user, selector, request_id)
908+
except Exception as e:
909+
logger.exception(e)
910+
return create_error_response(
911+
error_msg=ERROR_MARKING_RESOLVE_FAIL_LOG_MSG.format(
912+
request_id=request_id, error=str(str(e))
913+
)
914+
)
915+
916+
899917
def create_error_response(error_msg):
900918
json_response = {}
901919
json_response["error"] = error_msg

Access/admin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
GroupV2,
1010
MembershipV2,
1111
GroupAccessMapping,
12-
UserIdentity
12+
UserIdentity,
1313
)
1414

1515

Access/background_task_manager.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ def run_accept_request(data):
298298

299299
return {"status": False}
300300

301+
301302
def accept_request(user_access_mapping):
302303
try:
303304
result = run_access_grant.delay(user_access_mapping.request_id)

Access/group_helper.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,7 @@ def add_user_to_group(request):
526526
}
527527
return context
528528

529+
529530
def _check_if_members_in_group(group, selected_members):
530531
group_members_email = group.get_approved_and_pending_member_emails()
531532
duplicate_request_emails = set(selected_members).intersection(

Access/models.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,14 @@ def getPendingApprovalsCount(self, all_access_modules):
169169

170170
def getFailedGrantsCount(self):
171171
return (
172-
UserAccessMapping.objects.filter(status__in=["grantfailed"]).count()
172+
UserAccessMapping.objects.filter(status__in=["GrantFailed"]).count()
173173
if self.isAdminOrOps()
174174
else 0
175175
)
176176

177177
def getFailedRevokesCount(self):
178178
return (
179-
UserAccessMapping.objects.filter(status__in=["revokefailed"]).count()
179+
UserAccessMapping.objects.filter(status__in=["RevokeFailed"]).count()
180180
if self.isAdminOrOps()
181181
else 0
182182
)
@@ -570,7 +570,6 @@ def get_all_approved_members(self):
570570
group_members = self.get_all_members().filter(status="Approved")
571571
return group_members
572572

573-
574573
def get_approved_and_pending_member_emails(self):
575574
group_member_emails = self.membership_group.filter(
576575
status__in=["Approved", "Pending"]
@@ -878,10 +877,6 @@ def approve_access(self):
878877
self.status = "Approved"
879878
self.save()
880879

881-
@staticmethod
882-
def get_by_id(request_id):
883-
return UserAccessMapping.objects.get(request_id=request_id)
884-
885880
def revoking(self, revoker):
886881
self.revoker = revoker
887882
self.status = "ProcessingRevoke"

Access/notifications.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
)
2424
USER_ACCESS_REQUEST_GRANT_FAILURE_SUBJECT = "[Enigma][Access Management] {} - {} - {} \
2525
Failed to Approve Request"
26+
USER_REQUEST_RESOLVE_SUBJECT = "[Enigma][Access Management] - Request Resolved - {}"
2627

2728

2829
def send_new_group_create_notification(auth_user, date_time, new_group, member_list):
@@ -70,12 +71,13 @@ def send_membership_accepted_notification(user, group, membership):
7071
destination.append(user.email)
7172
general.emailSES(destination, subject, body)
7273

73-
def send_mulitple_membership_accepted_notification(user_names, group_name, membership):
74-
subject = MEMBERSHIP_ACCEPTED_SUBJECT.format(user_names, group_name)
74+
75+
def send_mulitple_membership_accepted_notification(user_names, group, membership):
76+
subject = MEMBERSHIP_ACCEPTED_SUBJECT.format(user_names, group.name)
7577
body = helpers.generateStringFromTemplate(
7678
filename="membershipAcceptedEmailBody.html",
77-
user_name= ",".join(user_names),
78-
group_name=group_name,
79+
user_name=",".join(user_names),
80+
group_name=group.name,
7981
approver=membership.approver.name,
8082
)
8183
destination = []
@@ -264,3 +266,16 @@ def send_decline_group_access_failed(destination, request_id, error):
264266
except Exception as e:
265267
logger.exception(str(e))
266268
logger.error("Something when wrong while sending Email.")
269+
270+
271+
def send_mail_for_request_resolve(auth_user, access_type, request_id):
272+
destination = [auth_user.email]
273+
subject = USER_REQUEST_RESOLVE_SUBJECT.format(request_id)
274+
body = helpers.generateStringFromTemplate(
275+
filename="requestResolvedEmail.html",
276+
user=auth_user.email,
277+
request_id=request_id,
278+
access_type=access_type,
279+
)
280+
general.emailSES(destination, subject, body)
281+
logger.debug("Email sent for " + subject + " to " + str(destination))

Access/views.py

Lines changed: 99 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
get_decline_access_request,
2323
accept_group_access,
2424
run_accept_request_task,
25+
run_ignore_failure_task,
2526
)
2627
from Access.models import User, UserAccessMapping, GroupAccessMapping
2728

@@ -611,7 +612,9 @@ def mark_revoked(request):
611612
for mapping_object in requests:
612613
logger.info(
613614
"Marking access revoke - %s by user %s",
614-
mapping_object.request_id, request.user.user)
615+
mapping_object.request_id,
616+
request.user.user,
617+
)
615618
mapping_object.revoke(revoker=request.user.user)
616619
success_list.append(mapping_object.request_id)
617620
json_response["msg"] = "Success"
@@ -621,23 +624,114 @@ def mark_revoked(request):
621624
json_response["error"] = "Error Revoking User Access"
622625
return JsonResponse(json_response, status=403)
623626

627+
624628
def individual_resolve(request):
625-
json_response = {"status_list":[]}
629+
json_response = {"status_list": []}
626630
try:
627-
request_ids = request.GET.getlist('requestId')
631+
request_ids = request.GET.getlist("requestId")
628632
if not request_ids:
629633
raise Exception("Request id not found in the request")
630634

631635
for request_id in request_ids:
632-
user_access_mapping = UserAccessMapping.get_by_id(request_id)
636+
user_access_mapping = UserAccessMapping.get_access_request(request_id)
633637
if user_access_mapping.status.lower() in ["grantfailed", "approved"]:
634-
response = run_accept_request_task(False, user_access_mapping, request.user, user_access_mapping.request_id, user_access_mapping.access.access_label)
638+
response = run_accept_request_task(
639+
False,
640+
user_access_mapping,
641+
request.user,
642+
user_access_mapping.request_id,
643+
user_access_mapping.access.access_label,
644+
)
635645
json_response["status_list"] += response["status"]
636646
else:
637647
json_response["status_list"].append({'title': 'The Request ('+request_id+') is already resolved.', 'msg': 'The request is already in final state.'})
638648
return render(request,'EnigmaOps/accessStatus.html',json_response)
639649
except Exception as e:
640650
logger.exception(str(e))
651+
json_response["error"] = {
652+
"error_msg": "Bad request",
653+
"msg": "Error in request not found OR Invalid request type",
654+
}
655+
return render(request, "BSOps/accessStatus.html", json_response)
656+
657+
658+
@login_required
659+
@user_with_permission([PERMISSION_CONSTANTS["DEFAULT_APPROVER_PERMISSION"]])
660+
def ignore_failure(request, selector):
661+
try:
662+
json_response = {"status_list": []}
663+
request_ids = request.GET.getlist("requestId")
664+
for request_id in request_ids:
665+
user_access_mapping = UserAccessMapping.get_access_request(request_id)
666+
if user_access_mapping.status.lower() in ["grantfailed", "revokefailed"]:
667+
run_ignore_failure_task(
668+
request.user,
669+
user_access_mapping,
670+
user_access_mapping.request_id,
671+
selector,
672+
)
673+
json_response["status_list"].append(
674+
{
675+
"title": "The Request ("
676+
+ request_id
677+
+ ") is now being ignored. Mark - "
678+
+ selector,
679+
"msg": "A email will be sent after the requested access is ignored",
680+
}
681+
)
682+
else:
683+
logger.debug("Cannot ignore " + request_id)
684+
json_response["status_list"].append(
685+
{
686+
"title": "The Request ("
687+
+ request_id
688+
+ ") is already resolved.",
689+
"msg": "The request is already in final state.",
690+
}
691+
)
692+
return render(request, "BSOps/accessStatus.html", json_response)
693+
except Exception as e:
694+
logger.debug("Error in request not found OR Invalid request type")
695+
logger.exception(e)
696+
json_response = {}
697+
json_response["error"] = {
698+
"error_msg": str(e),
699+
"msg": "Error in request not found OR Invalid request type",
700+
}
701+
return render(request, "BSOps/accessStatus.html", json_response)
702+
703+
704+
@login_required
705+
@user_with_permission([PERMISSION_CONSTANTS["DEFAULT_APPROVER_PERMISSION"]])
706+
def resolve_bulk(request):
707+
try:
708+
json_response = {"status_list": []}
709+
request_ids = request.GET.getlist("requestId")
710+
for request_id in request_ids:
711+
user_access_mapping = UserAccessMapping.get_access_request(request_id)
712+
if user_access_mapping.status.lower() in ["grantfailed"]:
713+
response = run_accept_request_task(
714+
False,
715+
user_access_mapping,
716+
request.user,
717+
user_access_mapping.request_id,
718+
user_access_mapping.access.access_label,
719+
)
720+
json_response["status_list"] += response["status"]
721+
else:
722+
json_response["status_list"].append(
723+
{
724+
"title": "The Request ("
725+
+ request_id
726+
+ ") is already resolved.",
727+
"msg": "The request is already in final state.",
728+
}
729+
)
730+
return render(request, "BSOps/accessStatus.html", json_response)
731+
except Exception as e:
732+
logger.debug("Error in request not found OR Invalid request type")
733+
logger.exception(e)
734+
json_response = {}
641735
json_response['error'] = {'error_msg': "Bad request", 'msg': "Error in request not found OR Invalid request type"}
642736
return render(request,'EnigmaOps/accessStatus.html',json_response)
643737

EnigmaAutomation/settings.py

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,11 @@
4747
"social_django",
4848
"Access",
4949
"rest_framework",
50-
'cid.apps.CidAppConfig',
50+
"cid.apps.CidAppConfig",
5151
]
5252
CID_GENERATE = True
53-
CID_GENERATOR = lambda: f'{time.time()}-{random.random()}'
54-
CID_HEADER = 'X_CORRELATION_ID'
53+
CID_GENERATOR = lambda: f"{time.time()}-{random.random()}"
54+
CID_HEADER = "X_CORRELATION_ID"
5555
CID_GENERATE = True
5656
CID_CONCATENATE_IDS = True
5757

@@ -203,26 +203,24 @@
203203
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
204204

205205
DATABASES = {}
206-
if data['database']['engine'] == "mysql":
207-
DATABASES['default'] = {
208-
'ENGINE': 'mysql.connector.django',
209-
'CONN_MAX_AGE': 0,
210-
'NAME': data['database']['dbname'],
211-
'USER': data['database']['username'],
212-
'PASSWORD': data['database']['password'],
213-
'HOST': data['database']['host'],
214-
'PORT': data['database']['port'],
215-
'OPTIONS': {
216-
'auth_plugin': 'mysql_native_password'
217-
}
206+
if data["database"]["engine"] == "mysql":
207+
DATABASES["default"] = {
208+
"ENGINE": "mysql.connector.django",
209+
"CONN_MAX_AGE": 0,
210+
"NAME": data["database"]["dbname"],
211+
"USER": data["database"]["username"],
212+
"PASSWORD": data["database"]["password"],
213+
"HOST": data["database"]["host"],
214+
"PORT": data["database"]["port"],
215+
"OPTIONS": {"auth_plugin": "mysql_native_password"},
218216
}
219-
elif data['database']['engine'] == "sqlite3":
220-
DATABASES['default'] = {
217+
elif data["database"]["engine"] == "sqlite3":
218+
DATABASES["default"] = {
221219
"ENGINE": "django.db.backends.sqlite3",
222220
"NAME": BASE_DIR / "db.sqlite3",
223221
}
224222
else:
225-
raise Exception("Database engine %s not recognized" % data['database']['engine'])
223+
raise Exception("Database engine %s not recognized" % data["database"]["engine"])
226224

227225
PERMISSION_CONSTANTS = {"DEFAULT_APPROVER_PERMISSION": "ACCESS_APPROVE"}
228226

@@ -233,7 +231,7 @@
233231

234232
ACCESS_MODULES = data["access_modules"]
235233

236-
AUTOMATED_EXEC_IDENTIFIER = 'automated-grant'
234+
AUTOMATED_EXEC_IDENTIFIER = "automated-grant"
237235

238236
LOGGING = {
239237
'version': 1,
@@ -251,36 +249,36 @@
251249
'filename': './enigma.log',
252250
'formatter': 'verbose',
253251
},
254-
'console': {
255-
'level': 'INFO',
256-
'class': 'logging.StreamHandler',
257-
'formatter': 'verbose',
252+
"console": {
253+
"level": "INFO",
254+
"class": "logging.StreamHandler",
255+
"formatter": "verbose",
258256
},
259257
},
260-
'loggers': {
261-
'django.request': {
262-
'handlers': ['file', 'console'],
263-
'level': 'INFO',
264-
'propagate': True,
265-
'formatter' : 'verbose',
258+
"loggers": {
259+
"django.request": {
260+
"handlers": ["file", "console"],
261+
"level": "INFO",
262+
"propagate": True,
263+
"formatter": "verbose",
266264
},
267-
'inventory': {
268-
'handlers': ['file', 'console'],
269-
'level': 'INFO',
270-
'propagate': True,
271-
'formatter' : 'verbose',
265+
"inventory": {
266+
"handlers": ["file", "console"],
267+
"level": "INFO",
268+
"propagate": True,
269+
"formatter": "verbose",
272270
},
273-
'Access':{
274-
'handlers': ['file', 'console'],
275-
'level': 'INFO',
276-
'propagate': True,
277-
'formatter' : 'verbose',
271+
"Access": {
272+
"handlers": ["file", "console"],
273+
"level": "INFO",
274+
"propagate": True,
275+
"formatter": "verbose",
278276
},
279-
'bootprocess':{
280-
'handlers': ['file', 'console'],
281-
'level': 'INFO',
282-
'propagate': True,
283-
'formatter' : 'verbose',
277+
"bootprocess": {
278+
"handlers": ["file", "console"],
279+
"level": "INFO",
280+
"propagate": True,
281+
"formatter": "verbose",
284282
},
285283
},
286284
}

0 commit comments

Comments
 (0)