-
Notifications
You must be signed in to change notification settings - Fork 749
Add API endpoint to apply new JCasC configuration with yaml in body #2815
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
Merged
+217
−0
Merged
Changes from 8 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
db632af
Add secure POST endpoint to apply JCasC YAML via HTTP body
somiljain2006 d48bdc9
Fix formatting error
somiljain2006 35cb7bb
Fix CSRF blocked coverage in tests
somiljain2006 3c5201d
Increasing readability and correctness
somiljain2006 b2f18ae
Replacing accumulate with put
somiljain2006 141da79
Remove the opt-in flag
somiljain2006 91fbda9
Remove unused line
somiljain2006 1634d5e
Apply suggestion from @timja
timja f919dbb
Apply suggestion from @timja
timja 13e9c85
Remove redundant lines from test class
somiljain2006 e220336
Remove redundant lines
somiljain2006 9afed0e
Fix formatting
somiljain2006 54ab408
Cleanup test
timja ebcfef5
Bit more
timja File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
298 changes: 298 additions & 0 deletions
298
plugin/src/test/java/io/jenkins/plugins/casc/ConfigurationAsCodeApiTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,298 @@ | ||
| package io.jenkins.plugins.casc; | ||
|
|
||
| import static org.junit.Assert.assertEquals; | ||
| import static org.junit.Assert.assertTrue; | ||
|
|
||
| import jakarta.servlet.ServletRequest; | ||
| import java.net.URL; | ||
| import jenkins.model.Jenkins; | ||
| import org.htmlunit.HttpMethod; | ||
| import org.htmlunit.WebRequest; | ||
| import org.htmlunit.WebResponse; | ||
| import org.junit.Rule; | ||
| import org.junit.Test; | ||
| import org.jvnet.hudson.test.JenkinsRule; | ||
| import org.jvnet.hudson.test.JenkinsRule.WebClient; | ||
| import org.jvnet.hudson.test.MockAuthorizationStrategy; | ||
|
|
||
| public class ConfigurationAsCodeApiTest { | ||
|
|
||
| @Rule | ||
| public JenkinsRule j = new JenkinsRule(); | ||
|
|
||
| @Test | ||
| public void testDoConfigure_RequiresPost() throws Exception { | ||
| j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); | ||
|
|
||
| j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy() | ||
| .grant(jenkins.model.Jenkins.ADMINISTER) | ||
| .everywhere() | ||
| .to("admin")); | ||
|
|
||
| WebClient wc = j.createWebClient(); | ||
| j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); | ||
| wc.login("admin", "admin"); | ||
| wc.setThrowExceptionOnFailingStatusCode(false); | ||
|
|
||
| WebRequest request = new WebRequest(new URL(j.getURL(), "configuration-as-code/configure"), HttpMethod.GET); | ||
| WebResponse response = wc.getPage(request).getWebResponse(); | ||
|
|
||
| assertTrue(response.getStatusCode() == 404 || response.getStatusCode() == 405); | ||
| } | ||
|
|
||
| @Test | ||
| public void testDoConfigure_Success() throws Exception { | ||
| j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); | ||
|
|
||
| j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy() | ||
| .grant(jenkins.model.Jenkins.ADMINISTER) | ||
| .everywhere() | ||
| .to("admin")); | ||
|
|
||
| WebClient wc = j.createWebClient(); | ||
| j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); | ||
| wc.login("admin", "admin"); | ||
| wc.setThrowExceptionOnFailingStatusCode(false); | ||
|
|
||
| WebRequest request = new WebRequest(new URL(j.getURL(), "configuration-as-code/configure"), HttpMethod.POST); | ||
| request.setAdditionalHeader("Content-Type", "application/yaml"); | ||
| request.setRequestBody("jenkins:\n systemMessage: 'Webhook Success'"); | ||
|
|
||
| var crumbIssuer = j.jenkins.getCrumbIssuer(); | ||
|
|
||
| if (crumbIssuer != null) { | ||
| request.setAdditionalHeader( | ||
| crumbIssuer.getCrumbRequestField(), crumbIssuer.getCrumb((ServletRequest) null)); | ||
| } | ||
|
|
||
| WebResponse response = wc.getPage(request).getWebResponse(); | ||
|
|
||
| assertEquals(200, response.getStatusCode()); | ||
| assertEquals("Webhook Success", j.jenkins.getSystemMessage()); | ||
| } | ||
|
|
||
| @Test | ||
| public void testDoConfigure_InvalidYaml() throws Exception { | ||
| j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); | ||
|
|
||
| j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy() | ||
| .grant(jenkins.model.Jenkins.ADMINISTER) | ||
| .everywhere() | ||
| .to("admin")); | ||
|
|
||
| WebClient wc = j.createWebClient(); | ||
| j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); | ||
| wc.login("admin", "admin"); | ||
| wc.setThrowExceptionOnFailingStatusCode(false); | ||
|
|
||
| WebRequest request = new WebRequest(new URL(j.getURL(), "configuration-as-code/configure"), HttpMethod.POST); | ||
|
|
||
| request.setRequestBody("jenkins:\n systemMessage: [invalid"); | ||
|
|
||
| var crumbIssuer = j.jenkins.getCrumbIssuer(); | ||
|
|
||
| if (crumbIssuer != null) { | ||
| request.setAdditionalHeader( | ||
| crumbIssuer.getCrumbRequestField(), crumbIssuer.getCrumb((ServletRequest) null)); | ||
| } | ||
|
|
||
| WebResponse response = wc.getPage(request).getWebResponse(); | ||
|
|
||
| assertEquals(400, response.getStatusCode()); | ||
| assertTrue(response.getContentAsString().contains("message")); | ||
| } | ||
|
|
||
| @Test | ||
| public void testDoConfigure_NonAdminForbidden() throws Exception { | ||
| j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); | ||
| j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy() | ||
| .grant(Jenkins.READ) | ||
| .everywhere() | ||
| .to("user") | ||
| .grant(jenkins.model.Jenkins.ADMINISTER) | ||
| .everywhere() | ||
| .to("admin")); | ||
|
somiljain2006 marked this conversation as resolved.
Outdated
|
||
|
|
||
| WebClient wc = j.createWebClient(); | ||
| wc.login("user", "user"); | ||
| wc.setThrowExceptionOnFailingStatusCode(false); | ||
|
|
||
| WebRequest request = new WebRequest(new URL(j.getURL(), "configuration-as-code/configure"), HttpMethod.POST); | ||
| request.setRequestBody("jenkins:\n systemMessage: 'fail'"); | ||
|
|
||
| var crumbIssuer = j.jenkins.getCrumbIssuer(); | ||
|
|
||
| if (crumbIssuer != null) { | ||
| request.setAdditionalHeader( | ||
| crumbIssuer.getCrumbRequestField(), crumbIssuer.getCrumb((ServletRequest) null)); | ||
| } | ||
|
somiljain2006 marked this conversation as resolved.
Outdated
|
||
|
|
||
| WebResponse response = wc.getPage(request).getWebResponse(); | ||
|
|
||
| assertEquals(403, response.getStatusCode()); | ||
| } | ||
|
|
||
| @Test | ||
| public void testDoConfigure_Unauthenticated() throws Exception { | ||
| j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); | ||
| j.jenkins.setAuthorizationStrategy(new org.jvnet.hudson.test.MockAuthorizationStrategy() | ||
| .grant(Jenkins.READ) | ||
| .everywhere() | ||
| .toEveryone()); | ||
|
|
||
| WebClient wc = j.createWebClient(); | ||
| wc.setThrowExceptionOnFailingStatusCode(false); | ||
|
|
||
| WebRequest request = new WebRequest(new URL(j.getURL(), "configuration-as-code/configure"), HttpMethod.POST); | ||
|
|
||
| request.setRequestBody("jenkins:\n systemMessage: 'anonymous bypass attempt'"); | ||
|
|
||
| WebResponse response = wc.getPage(request).getWebResponse(); | ||
|
|
||
| assertEquals(403, response.getStatusCode()); | ||
| } | ||
|
|
||
| @Test | ||
| public void testDoConfigure_MissingCrumb() throws Exception { | ||
|
somiljain2006 marked this conversation as resolved.
Outdated
|
||
| j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); | ||
| j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy() | ||
| .grant(Jenkins.ADMINISTER) | ||
| .everywhere() | ||
| .to("admin")); | ||
|
|
||
| WebClient wc = j.createWebClient(); | ||
| wc.login("admin", "admin"); | ||
| wc.setThrowExceptionOnFailingStatusCode(false); | ||
|
|
||
| WebRequest request = new WebRequest(new URL(j.getURL(), "configuration-as-code/configure"), HttpMethod.POST); | ||
|
|
||
| request.setRequestBody("jenkins:\n systemMessage: 'no crumb'"); | ||
|
|
||
| WebResponse response = wc.getPage(request).getWebResponse(); | ||
|
|
||
| assertEquals(403, response.getStatusCode()); | ||
| assertTrue(response.getContentAsString().contains("No valid crumb")); | ||
| } | ||
|
|
||
| @Test | ||
| public void testDoConfigure_EmptyBody() throws Exception { | ||
| j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); | ||
| j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy() | ||
| .grant(Jenkins.ADMINISTER) | ||
| .everywhere() | ||
| .to("admin")); | ||
|
|
||
| WebClient wc = j.createWebClient(); | ||
| wc.login("admin", "admin"); | ||
|
|
||
| wc.setThrowExceptionOnFailingStatusCode(false); | ||
|
|
||
| WebRequest request = new WebRequest(new URL(j.getURL(), "configuration-as-code/configure"), HttpMethod.POST); | ||
| request.setAdditionalHeader("Content-Type", "application/yaml"); | ||
|
|
||
| var crumbIssuer = j.jenkins.getCrumbIssuer(); | ||
|
|
||
| if (crumbIssuer != null) { | ||
| request.setAdditionalHeader( | ||
| crumbIssuer.getCrumbRequestField(), crumbIssuer.getCrumb((ServletRequest) null)); | ||
| } | ||
|
|
||
| WebResponse response = wc.getPage(request).getWebResponse(); | ||
|
|
||
| assertEquals(400, response.getStatusCode()); | ||
| assertTrue(response.getContentAsString().contains("message")); | ||
| } | ||
|
|
||
| @Test | ||
| public void testDoConfigure_MalformedStructure() throws Exception { | ||
| j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); | ||
|
|
||
| j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy() | ||
| .grant(jenkins.model.Jenkins.ADMINISTER) | ||
| .everywhere() | ||
| .to("admin")); | ||
|
|
||
| WebClient wc = j.createWebClient(); | ||
| j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); | ||
| wc.login("admin", "admin"); | ||
| wc.setThrowExceptionOnFailingStatusCode(false); | ||
|
|
||
| WebRequest request = new WebRequest(new URL(j.getURL(), "configuration-as-code/configure"), HttpMethod.POST); | ||
| request.setAdditionalHeader("Content-Type", "application/yaml"); | ||
| request.setRequestBody("jenkins:\n invalidRoot:\n foo: bar"); | ||
|
|
||
| var crumbIssuer = j.jenkins.getCrumbIssuer(); | ||
|
|
||
| if (crumbIssuer != null) { | ||
| request.setAdditionalHeader( | ||
| crumbIssuer.getCrumbRequestField(), crumbIssuer.getCrumb((ServletRequest) null)); | ||
| } | ||
|
|
||
| WebResponse response = wc.getPage(request).getWebResponse(); | ||
|
|
||
| assertEquals(400, response.getStatusCode()); | ||
| assertTrue(response.getContentAsString().contains("message")); | ||
| } | ||
|
|
||
| @Test | ||
| public void testDoConfigure_ValidYaml_NoChanges() throws Exception { | ||
| j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); | ||
| j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy() | ||
| .grant(Jenkins.ADMINISTER) | ||
| .everywhere() | ||
| .to("admin")); | ||
|
|
||
| WebClient wc = j.createWebClient(); | ||
| wc.login("admin", "admin"); | ||
| wc.setThrowExceptionOnFailingStatusCode(false); | ||
|
|
||
| WebRequest request1 = new WebRequest(new URL(j.getURL(), "configuration-as-code/configure"), HttpMethod.POST); | ||
| request1.setAdditionalHeader("Content-Type", "application/yaml"); | ||
| request1.setRequestBody("jenkins:\n systemMessage: 'Idempotency Test'"); | ||
| var crumbIssuer = j.jenkins.getCrumbIssuer(); | ||
| if (crumbIssuer != null) { | ||
| request1.setAdditionalHeader( | ||
| crumbIssuer.getCrumbRequestField(), crumbIssuer.getCrumb((ServletRequest) null)); | ||
| } | ||
|
|
||
| WebResponse response1 = wc.getPage(request1).getWebResponse(); | ||
| assertEquals(200, response1.getStatusCode()); | ||
| assertEquals("Idempotency Test", j.jenkins.getSystemMessage()); | ||
|
|
||
| WebRequest request2 = new WebRequest(new URL(j.getURL(), "configuration-as-code/configure"), HttpMethod.POST); | ||
| request2.setAdditionalHeader("Content-Type", "application/yaml"); | ||
| request2.setRequestBody("jenkins:\n systemMessage: 'Idempotency Test'"); | ||
| if (crumbIssuer != null) { | ||
| request2.setAdditionalHeader( | ||
| crumbIssuer.getCrumbRequestField(), crumbIssuer.getCrumb((ServletRequest) null)); | ||
| } | ||
|
|
||
| WebResponse response2 = wc.getPage(request2).getWebResponse(); | ||
|
|
||
| assertEquals(200, response2.getStatusCode()); | ||
| assertEquals("Idempotency Test", j.jenkins.getSystemMessage()); | ||
| } | ||
|
|
||
| @Test | ||
| public void testDoConfigure_WithApiToken_NoCrumb() throws Exception { | ||
| j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); | ||
| j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy() | ||
| .grant(Jenkins.ADMINISTER) | ||
| .everywhere() | ||
| .to("admin")); | ||
|
|
||
| WebClient wc = j.createWebClient(); | ||
|
|
||
| wc.withBasicApiToken("admin"); | ||
| wc.setThrowExceptionOnFailingStatusCode(false); | ||
|
|
||
| WebRequest request = new WebRequest(new URL(j.getURL(), "configuration-as-code/configure"), HttpMethod.POST); | ||
| request.setAdditionalHeader("Content-Type", "application/yaml"); | ||
| request.setRequestBody("jenkins:\n systemMessage: 'API Token Success'"); | ||
|
|
||
| WebResponse response = wc.getPage(request).getWebResponse(); | ||
|
|
||
| assertEquals(200, response.getStatusCode()); | ||
| assertEquals("API Token Success", j.jenkins.getSystemMessage()); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.