Skip to content

Commit

Permalink
Merge pull request #1830 from akto-api-security/fix/fix_dashboard_tem…
Browse files Browse the repository at this point in the history
…p_feedback

Fix/fix dashboard temp feedback
  • Loading branch information
Ark2307 authored Dec 20, 2024
2 parents 61bc259 + c1ae78a commit d014a2a
Show file tree
Hide file tree
Showing 10 changed files with 431 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ public String fetchSensitiveInfoInCollections(){
sensitiveSubtypes.addAll(SingleTypeInfoDao.instance.sensitiveSubTypeNames());

List<String> sensitiveSubtypesInRequest = SingleTypeInfoDao.instance.sensitiveSubTypeInRequestNames();
this.sensitiveUrlsInResponse = SingleTypeInfoDao.instance.getSensitiveApisCount(sensitiveSubtypes, true, Filters.empty());
this.sensitiveUrlsInResponse = SingleTypeInfoDao.instance.getSensitiveApisCount(sensitiveSubtypes, true, Filters.nin(SingleTypeInfo._COLLECTION_IDS, deactivatedCollections));

sensitiveSubtypes.addAll(sensitiveSubtypesInRequest);
this.sensitiveSubtypesInCollection = SingleTypeInfoDao.instance.getSensitiveSubtypesDetectedForCollection(sensitiveSubtypes);
Expand Down
303 changes: 267 additions & 36 deletions apps/dashboard/src/main/java/com/akto/action/JiraIntegrationAction.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
package com.akto.action;

import java.io.File;
import java.net.URL;
import java.util.*;
import java.util.concurrent.TimeUnit;

import com.akto.dao.test_editor.YamlTemplateDao;
import com.akto.dao.testing.TestingRunResultDao;
import com.akto.dto.ApiInfo;
import com.akto.dto.test_editor.Info;
import com.akto.dto.test_editor.YamlTemplate;
import com.akto.dto.test_run_findings.TestingRunIssues;
import com.akto.dto.testing.TestResult;
import com.akto.dto.testing.TestingRunResult;
import com.mongodb.client.model.Projections;
import org.apache.commons.io.FileUtils;
import org.bson.conversions.Bson;

Expand All @@ -15,6 +25,7 @@
import com.akto.dto.OriginalHttpResponse;
import com.akto.dto.jira_integration.JiraIntegration;
import com.akto.dto.jira_integration.JiraMetaData;
import com.akto.dto.test_run_findings.TestingIssuesId;
import com.akto.log.LoggerMaker;
import com.akto.log.LoggerMaker.LogDb;
import com.akto.parsers.HttpCallParser;
Expand Down Expand Up @@ -56,6 +67,7 @@ public class JiraIntegrationAction extends UserAction {

private final String META_ENDPOINT = "/rest/api/3/issue/createmeta";
private final String CREATE_ISSUE_ENDPOINT = "/rest/api/3/issue";
private final String CREATE_ISSUE_ENDPOINT_BULK = "/rest/api/3/issue/bulk";
private final String ATTACH_FILE_ENDPOINT = "/attachments";
private static final LoggerMaker loggerMaker = new LoggerMaker(ApiExecutor.class);
private static final OkHttpClient client = CoreHTTPClient.client.newBuilder()
Expand Down Expand Up @@ -189,42 +201,12 @@ public String fetchIntegration() {
public String createIssue() {

BasicDBObject reqPayload = new BasicDBObject();
BasicDBObject fields = new BasicDBObject();

String endpoint = jiraMetaData.getEndPointStr().replace("Endpoint - ", "");
String truncatedEndpoint = endpoint;
if(endpoint.length() > 30) {
truncatedEndpoint = endpoint.substring(0, 15) + "..." + endpoint.substring(endpoint.length() - 15);
}

String endpointMethod = jiraMetaData.getTestingIssueId().getApiInfoKey().getMethod().name();

// issue title
fields.put("summary", "Akto Report - " + jiraMetaData.getIssueTitle() + " (" + endpointMethod + " - " + truncatedEndpoint + ")");
jiraIntegration = JiraIntegrationDao.instance.findOne(new BasicDBObject());

// issue type (TASK)
BasicDBObject issueTypeObj = new BasicDBObject();
issueTypeObj.put("id", this.issueType);
fields.put("issuetype", issueTypeObj);

// project id
BasicDBObject project = new BasicDBObject();
project.put("key", this.projId);
fields.put("project", project);

// issue description
BasicDBObject description = new BasicDBObject();
description.put("type", "doc");
description.put("version", 1);
BasicDBList contentList = new BasicDBList();
contentList.add(buildContentDetails(jiraMetaData.getHostStr(), null));
contentList.add(buildContentDetails(jiraMetaData.getEndPointStr(), null));
contentList.add(buildContentDetails("Issue link - Akto dashboard", jiraMetaData.getIssueUrl()));
contentList.add(buildContentDetails(jiraMetaData.getIssueDescription(), null));
description.put("content", contentList);

fields.put("description", description);
if(jiraIntegration == null) {
addActionError("Jira is not integrated.");
return ERROR.toUpperCase();
}
BasicDBObject fields = jiraTicketPayloadCreator(jiraMetaData);

reqPayload.put("fields", fields);

Expand Down Expand Up @@ -376,6 +358,244 @@ private BasicDBObject buildContentDetails(String txt, String link) {
return details;
}

String aktoDashboardHost;
List<TestingIssuesId> issuesIds;
private String errorMessage;
public String bulkCreateJiraTickets (){
if(issuesIds == null || issuesIds.isEmpty()){
addActionError("Cannot create an empty jira issue.");
return ERROR.toUpperCase();
}

if((projId == null || projId.isEmpty()) || (issueType == null || issueType.isEmpty())){
addActionError("Project ID or Issue Type cannot be empty.");
return ERROR.toUpperCase();
}

jiraIntegration = JiraIntegrationDao.instance.findOne(new BasicDBObject());
if(jiraIntegration == null) {
addActionError("Jira is not integrated.");
return ERROR.toUpperCase();
}

List<JiraMetaData> jiraMetaDataList = new ArrayList<>();
Bson projection = Projections.include(YamlTemplate.INFO);
List<String> testingSubCategories = new ArrayList<>();
for(TestingIssuesId testingIssuesId : issuesIds) {
testingSubCategories.add(testingIssuesId.getTestSubCategory());
}
List<YamlTemplate> yamlTemplateList = YamlTemplateDao.instance.findAll(Filters.in("_id", testingSubCategories), projection);
Map<String, Info> testSubTypeToInfoMap = new HashMap<>();
for(YamlTemplate yamlTemplate : yamlTemplateList) {
if(yamlTemplate == null || yamlTemplate.getInfo() == null) {
loggerMaker.errorAndAddToDb("ERROR: YamlTemplate or YamlTemplate.info is null", LogDb.DASHBOARD);
continue;
}
Info info = yamlTemplate.getInfo();
testSubTypeToInfoMap.put(info.getSubCategory(), info);
}

List<TestingRunResult> testingRunResultList = new ArrayList<>();
int existingIssues = 0;
List<TestingRunIssues> testingRunIssuesList = TestingRunIssuesDao.instance.findAll(Filters.and(
Filters.in("_id", issuesIds),
Filters.exists("jiraIssueUrl", true)
));
Set<TestingIssuesId> testingRunIssueIds = new HashSet<>();
for (TestingRunIssues testingRunIssues : testingRunIssuesList) {
testingRunIssueIds.add(testingRunIssues.getId());
}

for(TestingIssuesId testingIssuesId : issuesIds) {
if(testingRunIssueIds.contains(testingIssuesId)) {
existingIssues++;
continue;
}

Info info = testSubTypeToInfoMap.get(testingIssuesId.getTestSubCategory());

TestingRunResult testingRunResult = TestingRunResultDao.instance.findOne(Filters.and(
Filters.in(TestingRunResult.TEST_SUB_TYPE, testingIssuesId.getTestSubCategory()),
Filters.in(TestingRunResult.API_INFO_KEY, testingIssuesId.getApiInfoKey())
), Projections.include("_id", TestingRunResult.TEST_RESULTS));

if(testingRunResult == null) {
loggerMaker.errorAndAddToDb("Error: Testing Run Result not found", LogDb.DASHBOARD);
continue;
}

testingRunResultList.add(testingRunResult);

JiraMetaData jiraMetaData;
try {
String inputUrl = testingIssuesId.getApiInfoKey().getUrl();

URL url = new URL(inputUrl);
String hostname = url.getHost();
String endpoint = url.getPath();

jiraMetaData = new JiraMetaData(
info.getName(),
"Host - "+hostname,
endpoint,
aktoDashboardHost+"/dashboard/issues?result="+testingRunResult.getId().toHexString(),
info.getDescription(),
testingIssuesId
);

} catch (Exception e) {
loggerMaker.errorAndAddToDb("Error while parsing the url: " + e.getMessage(), LogDb.DASHBOARD);
continue;
}

jiraMetaDataList.add(jiraMetaData);
}

BasicDBObject reqPayload = new BasicDBObject();
BasicDBList issueUpdates = new BasicDBList();

if(existingIssues == issuesIds.size()) {
errorMessage = "All selected issues already have existing Jira tickets. No new tickets were created.";
} else if(existingIssues > 0) {
errorMessage = "Jira tickets created for all selected issues, except for " + existingIssues + " issues that already have tickets.";
}

if(jiraMetaDataList.isEmpty()) {
return Action.SUCCESS.toUpperCase();
}

for (JiraMetaData jiraMetaData : jiraMetaDataList) {
BasicDBObject fields = jiraTicketPayloadCreator(jiraMetaData);

// Prepare the issue object
BasicDBObject issueObject = new BasicDBObject();
issueObject.put("fields", fields);
issueUpdates.add(issueObject);
}

// Prepare the full request payload
reqPayload.put("issueUpdates", issueUpdates);

// URL for bulk create issues
String url = jiraIntegration.getBaseUrl() + CREATE_ISSUE_ENDPOINT_BULK;
String authHeader = Base64.getEncoder().encodeToString((jiraIntegration.getUserEmail() + ":" + jiraIntegration.getApiToken()).getBytes());

Map<String, List<String>> headers = new HashMap<>();
headers.put("Authorization", Collections.singletonList("Basic " + authHeader));

OriginalHttpRequest request = new OriginalHttpRequest(url, "", "POST", reqPayload.toString(), headers, "");

try {
OriginalHttpResponse response = ApiExecutor.sendRequest(request, true, null, false, new ArrayList<>());
String responsePayload = response.getBody();

if (response.getStatusCode() > 201 || responsePayload == null) {
loggerMaker.errorAndAddToDb("Error while creating Jira issues in bulk, URL not accessible, request body "
+ request.getBody() + " ,response body " + response.getBody() + " ,response status " + response.getStatusCode(),
LoggerMaker.LogDb.DASHBOARD);

if (responsePayload != null) {
try {
BasicDBObject obj = BasicDBObject.parse(responsePayload);
List<String> errorMessages = (List) obj.get("errorMessages");
String error;
if (errorMessages.size() == 0) {
BasicDBObject errObj = BasicDBObject.parse(obj.getString("errors"));
error = errObj.getString("project");
} else {
error = errorMessages.get(0);
}
addActionError(error);
} catch (Exception e) {
// Handle exception
}
}

return Action.ERROR.toUpperCase();
}

BasicDBObject payloadObj;
try {
payloadObj = BasicDBObject.parse(responsePayload);
List<BasicDBObject> issues = (List<BasicDBObject>) payloadObj.get("issues");
for (BasicDBObject issue : issues) {
String issueKey = issue.getString("key");
String jiraTicketUrl = jiraIntegration.getBaseUrl() + "/browse/" + issueKey;

for (JiraMetaData metaData : jiraMetaDataList) {
TestingRunIssuesDao.instance.getMCollection().updateOne(
Filters.eq(Constants.ID, metaData.getTestingIssueId()),
Updates.combine(Updates.set("jiraIssueUrl", jiraTicketUrl)),
new UpdateOptions().upsert(false)
);
}
}

for (int i = 0; i < issues.size(); i++) {
BasicDBObject issue = issues.get(i);
TestingRunResult testingRunResult = testingRunResultList.get(i);
String issueKey = issue.getString("key");
TestResult genericTestResult = (TestResult) testingRunResult.getTestResults().get(testingRunResult.getTestResults().size() - 1);
setIssueId(issueKey);
setOrigReq(genericTestResult.getOriginalMessage());
setTestReq(genericTestResult.getMessage());
String status = attachFileToIssue();
if (status.equals(ERROR.toUpperCase())) {
return ERROR.toUpperCase();
}
}
} catch (Exception e) {
loggerMaker.errorAndAddToDb(e, "Error processing Jira bulk issue response " + e.getMessage(), LoggerMaker.LogDb.DASHBOARD);
return Action.ERROR.toUpperCase();
}

} catch (Exception e) {
loggerMaker.errorAndAddToDb(e, "Error making Jira bulk create request: " + e.getMessage(), LoggerMaker.LogDb.DASHBOARD);
return Action.ERROR.toUpperCase();
}

return Action.SUCCESS.toUpperCase();
}

private BasicDBObject jiraTicketPayloadCreator(JiraMetaData jiraMetaData) {
BasicDBObject fields = new BasicDBObject();
String endpoint = jiraMetaData.getEndPointStr().replace("Endpoint - ", "");
String truncatedEndpoint = endpoint;
if(endpoint.length() > 30) {
truncatedEndpoint = endpoint.substring(0, 15) + "..." + endpoint.substring(endpoint.length() - 15);
}

String endpointMethod = jiraMetaData.getTestingIssueId().getApiInfoKey().getMethod().name();

// issue title
fields.put("summary", "Akto Report - " + jiraMetaData.getIssueTitle() + " (" + endpointMethod + " - " + truncatedEndpoint + ")");

// Issue type (TASK)
BasicDBObject issueTypeObj = new BasicDBObject();
issueTypeObj.put("id", this.issueType);
fields.put("issuetype", issueTypeObj);

// Project ID
BasicDBObject project = new BasicDBObject();
project.put("key", this.projId);
fields.put("project", project);

// Issue description
BasicDBObject description = new BasicDBObject();
description.put("type", "doc");
description.put("version", 1);
BasicDBList contentList = new BasicDBList();
contentList.add(buildContentDetails(jiraMetaData.getHostStr(), null));
contentList.add(buildContentDetails(jiraMetaData.getEndPointStr(), null));
contentList.add(buildContentDetails("Issue link - Akto dashboard", jiraMetaData.getIssueUrl()));
contentList.add(buildContentDetails(jiraMetaData.getIssueDescription(), null));
description.put("content", contentList);

fields.put("description", description);

return fields;
}

public String getBaseUrl() {
return baseUrl;
}
Expand Down Expand Up @@ -467,5 +687,16 @@ public String getJiraTicketKey() {
public void setJiraTicketKey(String jiraTicketKey) {
this.jiraTicketKey = jiraTicketKey;
}


public void setIssuesIds(List<TestingIssuesId> issuesIds) {
this.issuesIds = issuesIds;
}

public void setAktoDashboardHost(String aktoDashboardHost) {
this.aktoDashboardHost = aktoDashboardHost;
}

public String getErrorMessage() {
return errorMessage;
}
}
22 changes: 22 additions & 0 deletions apps/dashboard/src/main/resources/struts.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6343,6 +6343,28 @@
</result>
</action>

<action name="api/bulkCreateJiraTickets" class="com.akto.action.JiraIntegrationAction" method="bulkCreateJiraTickets" >
<interceptor-ref name="json"/>
<interceptor-ref name="defaultStack" />
<interceptor-ref name="roleAccessInterceptor">
<param name="featureLabel">ISSUES</param>
<param name="accessType">READ_WRITE</param>
<param name="actionDescription">User created jira issues in bulk</param>
</interceptor-ref>

<result name="FORBIDDEN" type="json">
<param name="statusCode">403</param>
<param name="ignoreHierarchy">false</param>
<param name="includeProperties">^actionErrors.*</param>
</result>
<result name="SUCCESS" type="json"/>
<result name="ERROR" type="json">
<param name="statusCode">422</param>
<param name="ignoreHierarchy">false</param>
<param name="includeProperties">^actionErrors.*, ^responses.*</param>
</result>
</action>

<action name="api/attachFileToIssue" class="com.akto.action.JiraIntegrationAction" method="attachFileToIssue" >
<interceptor-ref name="json"/>
<interceptor-ref name="defaultStack" />
Expand Down
Loading

0 comments on commit d014a2a

Please sign in to comment.