Skip to content

Commit 3041720

Browse files
committed
CET-19215 add secret scanning
1 parent f2941fe commit 3041720

File tree

5 files changed

+389
-0
lines changed

5 files changed

+389
-0
lines changed

src/main/java/org/kohsuke/github/GHRepository.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3496,6 +3496,47 @@ public <T> void dispatch(String eventType, @Nullable T clientPayload) throws IOE
34963496
.send();
34973497
}
34983498

3499+
/**
3500+
* Lists the secret scanning alerts for this repository
3501+
*
3502+
* @return the paged iterable
3503+
*/
3504+
public PagedIterable<GHSecretScanningAlert> listSecretScanningAlerts() {
3505+
return listSecretScanningAlerts(Collections.emptyMap());
3506+
}
3507+
3508+
/**
3509+
* Lists the secret scanning alerts for this repository filtered on the alert state
3510+
*
3511+
* @param state
3512+
* state of the alert
3513+
* @return the paged iterable
3514+
*/
3515+
public PagedIterable<GHSecretScanningAlert> listSecretScanningAlerts(GHSecretScanningAlertState state) {
3516+
return listSecretScanningAlerts(Collections.singletonMap("state", state.name().toLowerCase()));
3517+
}
3518+
3519+
private PagedIterable<GHSecretScanningAlert> listSecretScanningAlerts(Map<String, Object> filters) {
3520+
return new GHSecretScanningAlertsIterable(this,
3521+
root().createRequest().withUrlPath(getApiTailUrl("secret-scanning/alerts")).with(filters).build());
3522+
}
3523+
3524+
/**
3525+
* Get secret scanning alert by number
3526+
*
3527+
* @param number
3528+
* number of the secret scanning alert
3529+
* @return the secret scanning alert
3530+
* @throws IOException
3531+
* the io exception
3532+
*/
3533+
public GHSecretScanningAlert getSecretScanningAlert(long number) throws IOException {
3534+
return root().createRequest()
3535+
.withUrlPath(getApiTailUrl("secret-scanning/alerts"), String.valueOf(number))
3536+
.fetch(GHSecretScanningAlert.class)
3537+
.wrap(this);
3538+
}
3539+
34993540
private <T> T downloadArchive(@Nonnull String type,
35003541
@CheckForNull String ref,
35013542
@Nonnull InputStreamFunction<T> streamFunction) throws IOException {
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
package org.kohsuke.github;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnore;
4+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
5+
6+
import java.io.IOException;
7+
import java.net.URL;
8+
import java.util.Date;
9+
10+
/**
11+
* Secret scanning alert for a repository
12+
*
13+
* <a href="https://docs.github.com/en/rest/secret-scanning/secret-scanning"></a>
14+
*/
15+
@SuppressFBWarnings(value = { "UUF_UNUSED_FIELD" }, justification = "JSON API")
16+
public class GHSecretScanningAlert extends GHObject {
17+
@JsonIgnore
18+
private GHRepository owner;
19+
private long number;
20+
private String html_url;
21+
private GHSecretScanningAlertState state;
22+
private String resolution;
23+
private String resolved_at;
24+
private GHUser resolved_by;
25+
private String secret_type;
26+
private Secret secret;
27+
private String push_protection_bypassed;
28+
private GHUser push_protection_bypassed_by;
29+
private String push_protection_bypassed_at;
30+
31+
GHSecretScanningAlert wrap(GHRepository owner) {
32+
this.owner = owner;
33+
return this;
34+
}
35+
36+
/**
37+
* Id/number of the alert.
38+
*
39+
* @return the id/number
40+
* @see #getId()
41+
*/
42+
public long getNumber() {
43+
return number;
44+
}
45+
46+
/**
47+
* Id/number of the alert.
48+
*
49+
* @return the id/number
50+
* @see #getNumber()
51+
*/
52+
@Override
53+
public long getId() {
54+
return getNumber();
55+
}
56+
57+
/**
58+
* State of alert
59+
*
60+
* @return the state
61+
*/
62+
public GHSecretScanningAlertState getState() {
63+
return state;
64+
}
65+
66+
/**
67+
* Resolution of the alert. Can be 'false_positive', 'wont_fix', 'revoked', 'used_in_tests', or null.
68+
*
69+
* @return the resolution
70+
*/
71+
public String getResolution() {
72+
return resolution;
73+
}
74+
75+
/**
76+
* Time when alert was resolved. Non-null when {@link #getState()} is <i>Resolved</i>
77+
*
78+
* @return the time
79+
*/
80+
public Date getResolvedAt() {
81+
return GitHubClient.parseDate(resolved_at);
82+
}
83+
84+
/**
85+
* User that has resolved the alert. Non-null when {@link #getState()} is <i>Resolved</i>
86+
*
87+
* <p>
88+
* Note: User object returned by secret scanning GitHub API does not contain all fields. Use with caution
89+
* </p>
90+
*
91+
* @return the user
92+
*/
93+
@SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior")
94+
public GHUser getResolvedBy() {
95+
return resolved_by;
96+
}
97+
98+
/**
99+
* Type of secret that was detected
100+
*
101+
* @return the secret type
102+
*/
103+
public String getSecretType() {
104+
return secret_type;
105+
}
106+
107+
/**
108+
* Secret that was detected
109+
*
110+
* @return the secret
111+
*/
112+
public Secret getSecret() {
113+
return secret;
114+
}
115+
116+
/**
117+
* Whether push protection was bypassed for this alert
118+
*
119+
* @return true if push protection was bypassed, false otherwise
120+
*/
121+
public boolean isPushProtectionBypassed() {
122+
return push_protection_bypassed != null && !push_protection_bypassed.isEmpty();
123+
}
124+
125+
/**
126+
* User that bypassed push protection. Non-null when {@link #isPushProtectionBypassed()} is true
127+
*
128+
* @return the user
129+
*/
130+
@SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior")
131+
public GHUser getPushProtectionBypassedBy() {
132+
return push_protection_bypassed_by;
133+
}
134+
135+
/**
136+
* Time when push protection was bypassed. Non-null when {@link #isPushProtectionBypassed()} is true
137+
*
138+
* @return the time
139+
*/
140+
public Date getPushProtectionBypassedAt() {
141+
return GitHubClient.parseDate(push_protection_bypassed_at);
142+
}
143+
144+
@Override
145+
public URL getHtmlUrl() throws IOException {
146+
return GitHubClient.parseURL(html_url);
147+
}
148+
149+
/**
150+
* Secret details
151+
*/
152+
@SuppressFBWarnings(value = { "UWF_UNWRITTEN_FIELD" }, justification = "JSON API")
153+
public static class Secret {
154+
private String name;
155+
private String type;
156+
private String value;
157+
158+
/**
159+
* Name of the secret
160+
*
161+
* @return the name
162+
*/
163+
public String getName() {
164+
return name;
165+
}
166+
167+
/**
168+
* Type of the secret
169+
*
170+
* @return the type
171+
*/
172+
public String getType() {
173+
return type;
174+
}
175+
176+
/**
177+
* Value of the secret
178+
*
179+
* @return the value
180+
*/
181+
public String getValue() {
182+
return value;
183+
}
184+
}
185+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.kohsuke.github;
2+
3+
/**
4+
* What is the current state of the Secret Scanning Alert
5+
*/
6+
public enum GHSecretScanningAlertState {
7+
/**
8+
* Alert is open and still an active issue.
9+
*/
10+
OPEN,
11+
/**
12+
* Issue that has caused the alert has been addressed.
13+
*/
14+
RESOLVED,
15+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package org.kohsuke.github;
2+
3+
import java.util.Iterator;
4+
5+
import javax.annotation.Nonnull;
6+
7+
class GHSecretScanningAlertsIterable extends PagedIterable<GHSecretScanningAlert> {
8+
private final GHRepository owner;
9+
private final GitHubRequest request;
10+
private GHSecretScanningAlert[] result;
11+
12+
GHSecretScanningAlertsIterable(GHRepository owner, GitHubRequest request) {
13+
this.owner = owner;
14+
this.request = request;
15+
}
16+
17+
@Nonnull
18+
@Override
19+
public PagedIterator<GHSecretScanningAlert> _iterator(int pageSize) {
20+
return new PagedIterator<>(
21+
adapt(GitHubPageIterator
22+
.create(owner.root().getClient(), GHSecretScanningAlert[].class, request, pageSize)),
23+
null);
24+
}
25+
26+
protected Iterator<GHSecretScanningAlert[]> adapt(final Iterator<GHSecretScanningAlert[]> base) {
27+
return new Iterator<GHSecretScanningAlert[]>() {
28+
public boolean hasNext() {
29+
return base.hasNext();
30+
}
31+
32+
public GHSecretScanningAlert[] next() {
33+
GHSecretScanningAlert[] v = base.next();
34+
if (result == null) {
35+
result = v;
36+
}
37+
38+
for (GHSecretScanningAlert alert : result) {
39+
alert.wrap(owner);
40+
}
41+
return result;
42+
}
43+
};
44+
}
45+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package org.kohsuke.github;
2+
3+
import org.junit.Assume;
4+
import org.junit.Before;
5+
import org.junit.Test;
6+
7+
import java.io.IOException;
8+
import java.util.List;
9+
10+
import static org.hamcrest.Matchers.*;
11+
12+
/**
13+
* <p>
14+
* Note : As the code scanning alerts cannot be tailored as part of test setup, lot of the test cases are dependent on
15+
* manual setup of the mock repo. Assertions and verifications will often simply check that the values are non-null
16+
* rather than depending on hard-coded values, to prevent making the tests flimsy
17+
* </p>
18+
*/
19+
public class GHSecretScanningAlertTest extends AbstractGitHubWireMockTest {
20+
private static final String REPO_NAME = "Pixi";
21+
private GHRepository repo;
22+
23+
/**
24+
* Gets the mock repo
25+
*
26+
* @throws Exception
27+
* the exception
28+
*/
29+
@Before
30+
public void setUp() throws Exception {
31+
repo = gitHub.getRepository("cortextests" + "/" + "test-code-scanning");
32+
}
33+
34+
/**
35+
* Test list code scanning alert payload
36+
*/
37+
@Test
38+
public void testListSecretScanningAlerts() {
39+
// Arrange
40+
41+
// Act
42+
List<GHSecretScanningAlert> alerts = repo.listSecretScanningAlerts()._iterator(2).nextPage();
43+
44+
// Assert
45+
assertThat(alerts.size(), equalTo(2)); // This assertion is based on manual setup done on repo to
46+
// guarantee there are atleast 2 issues
47+
48+
// GHCodeScanningAlert alert = codeQlAlerts.get(0);
49+
//
50+
// // Verify the code scanning tool details
51+
// assertThat(alert.getTool(), not((Object) null));
52+
// GHCodeScanningAlert.Tool tool = alert.getTool();
53+
// assertThat(tool.getName(), is("CodeQL"));
54+
// assertThat(tool.getVersion(), not((Object) null));
55+
//
56+
// // Verify that fields of the code scanning rule are non-null
57+
// assertThat(alert.getRule(), not((Object) null));
58+
// GHCodeScanningAlert.Rule rule = alert.getRule();
59+
// assertThat(rule.getId(), not((Object) null));
60+
// assertThat(rule.getName(), not((Object) null));
61+
// assertThat(rule.getSeverity(), not((Object) null));
62+
// assertThat(rule.getSecuritySeverityLevel(), not((Object) null));
63+
//
64+
// // Act - Search by filtering on alert status
65+
// List<GHCodeScanningAlert> openAlerts = repo.listCodeScanningAlerts(GHCodeScanningAlertState.OPEN)
66+
// ._iterator(2)
67+
// .nextPage(); // This assertion is based on manual setup done on repo to
68+
// // guarantee there are atleast 2 issues
69+
//
70+
// // Assert
71+
// assertThat(openAlerts.size(), equalTo(2));
72+
// GHCodeScanningAlert openAlert = openAlerts.get(0);
73+
// assertThat(openAlert.getState(), is(GHCodeScanningAlertState.OPEN));
74+
}
75+
76+
/**
77+
* Test get code scanning alert payload
78+
*
79+
* @throws IOException
80+
* Signals that an I/O exception has occurred.
81+
*/
82+
@Test
83+
public void testGetCodeScanningAlert() throws IOException {
84+
// Arrange
85+
List<GHCodeScanningAlert> dismissedAlerts = repo.listCodeScanningAlerts(GHCodeScanningAlertState.DISMISSED)
86+
._iterator(1)
87+
.nextPage();
88+
Assume.assumeThat(dismissedAlerts.size(), greaterThanOrEqualTo(1));
89+
GHCodeScanningAlert dismissedAlert = dismissedAlerts.get(0);
90+
long idOfDismissed = dismissedAlert.getId();
91+
92+
// Act
93+
GHCodeScanningAlert result = repo.getCodeScanningAlert(idOfDismissed);
94+
95+
// Assert
96+
assertThat(result, not((Object) null));
97+
assertThat(result.getId(), equalTo(idOfDismissed));
98+
assertThat(result.getDismissedReason(), equalTo(dismissedAlert.getDismissedReason()));
99+
assertThat(result.getDismissedAt(), equalTo(dismissedAlert.getDismissedAt()));
100+
assertThat(result.getDismissedBy().login, equalTo(dismissedAlert.getDismissedBy().login));
101+
}
102+
103+
}

0 commit comments

Comments
 (0)