From c59825e9ea63cc0d8a7fd75acbe96e983d1826ab Mon Sep 17 00:00:00 2001 From: David Olorundare Date: Mon, 11 Mar 2019 20:30:40 -0500 Subject: [PATCH 1/5] [JENKINS-55694] Add user property event log-listeners --- .../listeners/ApiKeyCreationListener.java | 64 +++++++++++++++++++ .../listeners/ApiKeyDeletionListener.java | 64 +++++++++++++++++++ .../listeners/UserPasswordLogListener.java | 61 ++++++++++++++++++ 3 files changed, 189 insertions(+) create mode 100644 src/main/java/io/jenkins/plugins/audit/listeners/ApiKeyCreationListener.java create mode 100644 src/main/java/io/jenkins/plugins/audit/listeners/ApiKeyDeletionListener.java create mode 100644 src/main/java/io/jenkins/plugins/audit/listeners/UserPasswordLogListener.java diff --git a/src/main/java/io/jenkins/plugins/audit/listeners/ApiKeyCreationListener.java b/src/main/java/io/jenkins/plugins/audit/listeners/ApiKeyCreationListener.java new file mode 100644 index 0000000..ad2d5a0 --- /dev/null +++ b/src/main/java/io/jenkins/plugins/audit/listeners/ApiKeyCreationListener.java @@ -0,0 +1,64 @@ +package io.jenkins.plugins.audit.listeners; + +/* + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import hudson.Extension; +import hudson.ExtensionList; +import hudson.model.UserProperty; + +import io.jenkins.plugins.audit.event.CreateKey; +import jenkins.security.ApiTokenProperty; +import jenkins.security.ApiTokenPropertyListener; +import org.apache.logging.log4j.audit.LogEventFactory; + +import javax.annotation.Nonnull; + +/** + * Listener notified of api token key creation events. + */ +@Extension +public class ApiKeyCreationListener extends ApiTokenPropertyListener { + + /** + * Fired when a new user property has been created. + * + * @param username the user + * @param value property that was newly created. + * + */ + @Override + public void onCreated(@Nonnull String username, @Nonnull UserProperty value) { + if (value instanceof ApiTokenProperty) { + CreateKey user = LogEventFactory.getEvent(CreateKey.class); + + user.setUserId(username); + user.logEvent(); + } + } + + /** + * Returns a registered {@link ApiKeyCreationListener} instance. + */ + public static ExtensionList getInstance() { return ExtensionList.lookup(ApiKeyCreationListener.class); } + +} diff --git a/src/main/java/io/jenkins/plugins/audit/listeners/ApiKeyDeletionListener.java b/src/main/java/io/jenkins/plugins/audit/listeners/ApiKeyDeletionListener.java new file mode 100644 index 0000000..c2fe55f --- /dev/null +++ b/src/main/java/io/jenkins/plugins/audit/listeners/ApiKeyDeletionListener.java @@ -0,0 +1,64 @@ +package io.jenkins.plugins.audit.listeners; + +/* + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import hudson.Extension; +import hudson.ExtensionList; +import hudson.model.UserProperty; + +import io.jenkins.plugins.audit.event.DeleteKey; +import jenkins.security.ApiTokenProperty; +import jenkins.security.ApiTokenPropertyListener; +import org.apache.logging.log4j.audit.LogEventFactory; + +import javax.annotation.Nonnull; + +/** + * Listener notified of api token key deletion or revocation events. + */ +@Extension +public class ApiKeyDeletionListener extends ApiTokenPropertyListener { + + /** + * Fired when an api token has been revoked + * + * @param username id of the user + * @param value api token property of the user + * + */ + @Override + public void onDeleted(@Nonnull String username, @Nonnull UserProperty value) { + if (value instanceof ApiTokenProperty) { + DeleteKey user = LogEventFactory.getEvent(DeleteKey.class); + + user.setUserId(username); + user.logEvent(); + } + } + + /** + * Returns a registered {@link ApiKeyDeletionListener} instance. + */ + public static ExtensionList getInstance() { return ExtensionList.lookup(ApiKeyDeletionListener.class); } + +} diff --git a/src/main/java/io/jenkins/plugins/audit/listeners/UserPasswordLogListener.java b/src/main/java/io/jenkins/plugins/audit/listeners/UserPasswordLogListener.java new file mode 100644 index 0000000..8e2353f --- /dev/null +++ b/src/main/java/io/jenkins/plugins/audit/listeners/UserPasswordLogListener.java @@ -0,0 +1,61 @@ +package io.jenkins.plugins.audit.listeners; + +/* + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import hudson.security.PasswordPropertyListener; +import io.jenkins.plugins.audit.event.UpdatePassword; +import org.apache.logging.log4j.audit.LogEventFactory; + +import hudson.Extension; +import hudson.ExtensionList; + +import javax.annotation.Nonnull; + +/** + * Listener which logs password-update audit events. + */ +@Extension +public class UserPasswordLogListener extends PasswordPropertyListener { + + /** + * Fired when a user password property has been updated and will log the event. + * + * @param username the user + * @param oldValue old property of the user + * @param newValue new property of the user + * + */ + @Override + public void onChanged(@Nonnull String username, @Nonnull Object oldValue, @Nonnull Object newValue) { + UpdatePassword user = LogEventFactory.getEvent(UpdatePassword.class); + + user.setUserId(username); + user.logEvent(); + } + + /** + * Returns a registered {@link UserPasswordLogListener} instance. + */ + public static ExtensionList getInstance() { return ExtensionList.lookup(UserPasswordLogListener.class); } + +} From f0c6354620a60dfd19799bb485fa5861df910b2c Mon Sep 17 00:00:00 2001 From: David Olorundare Date: Mon, 11 Mar 2019 20:33:31 -0500 Subject: [PATCH 2/5] [JENKINS-55694] Add user-property listener unittests --- .../listeners/ApiKeyCreationListenerTest.java | 126 +++++++++++++ .../listeners/ApiKeyDeletionListenerTest.java | 141 +++++++++++++++ .../UserPasswordLogListenerTest.java | 166 ++++++++++++++++++ 3 files changed, 433 insertions(+) create mode 100644 src/test/java/io/jenkins/plugins/audit/listeners/ApiKeyCreationListenerTest.java create mode 100644 src/test/java/io/jenkins/plugins/audit/listeners/ApiKeyDeletionListenerTest.java create mode 100644 src/test/java/io/jenkins/plugins/audit/listeners/UserPasswordLogListenerTest.java diff --git a/src/test/java/io/jenkins/plugins/audit/listeners/ApiKeyCreationListenerTest.java b/src/test/java/io/jenkins/plugins/audit/listeners/ApiKeyCreationListenerTest.java new file mode 100644 index 0000000..7789271 --- /dev/null +++ b/src/test/java/io/jenkins/plugins/audit/listeners/ApiKeyCreationListenerTest.java @@ -0,0 +1,126 @@ +package io.jenkins.plugins.audit.listeners; + +/* + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import hudson.security.HudsonPrivateSecurityRealm; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.HashMap; +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.URL; + +import com.gargoylesoftware.htmlunit.WebRequest; +import com.gargoylesoftware.htmlunit.HttpMethod; +import com.gargoylesoftware.htmlunit.Page; +import jenkins.security.ApiTokenProperty; +import org.apache.commons.lang.StringUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.jvnet.hudson.test.Issue; +import org.xml.sax.SAXException; +import org.acegisecurity.Authentication; +import org.jvnet.hudson.test.JenkinsRule; +import org.apache.logging.log4j.core.LogEvent; +import org.jvnet.hudson.test.JenkinsRule.WebClient; +import org.apache.logging.log4j.test.appender.ListAppender; +import org.apache.logging.log4j.message.StructuredDataMessage; + +import com.gargoylesoftware.htmlunit.html.HtmlPage; + +import static org.junit.Assert.*; + +import hudson.model.User; +import jenkins.model.Jenkins; +import hudson.security.pages.SignupPage; +import hudson.security.HudsonPrivateSecurityRealm; + +public class ApiKeyCreationListenerTest { + + private ListAppender app; + private WebClient client; + private final HashMap USERS = new HashMap(); + + private static void assertEventCount(final List events, final int expected) { + assertEquals("Incorrect number of events.", expected, events.size()); + } + + private static WebClient logout(final WebClient wc) throws IOException, SAXException { + wc.goTo("logout"); + return wc; + } + + @Rule + public JenkinsRule j = new JenkinsRule(); + //{j.timeout = 0;} // disable timeouts + + @Before + public void setup() throws Exception { + // user ID conformance check + Field field = HudsonPrivateSecurityRealm.class.getDeclaredField("ID_REGEX"); + field.setAccessible(true); + field.set(null, null); + + client = j.createWebClient(); + logout(client); + + app = ListAppender.getListAppender("AuditList").clear(); + } + + @After + public void teardown() { + app.clear(); + } + + @Issue("JENKINS-55694") + @Test + public void createUserToken() throws Exception { + HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(true, false, null); + j.jenkins.setSecurityRealm(securityRealm); + + List events = app.getEvents(); + assertEventCount(events, 0); + + // new user account creation + SignupPage signup = new SignupPage(client.goTo("signup")); + signup.enterUsername("charlie"); + signup.enterPassword("charlie"); + signup.enterFullName(StringUtils.capitalize("charlie user")); + HtmlPage page = signup.submit(j); + + // execute an http request to create a new a user api token from their config page + User charlie = User.getById("charlie", false); + URL configPage = client.createCrumbedUrl(charlie.getUrl() + "/" + "/descriptorByName/" + ApiTokenProperty.class.getName() + "/generateNewToken/?newTokenName=" + "charlie-token"); + Page p = client.getPage(new WebRequest(configPage, HttpMethod.POST)); + + // ensure user whose api token was created was in fact logged + StructuredDataMessage logMessage = (StructuredDataMessage) events.get(2).getMessage(); + assertTrue(logMessage.toString().contains("createKey")); + assertEventCount(events, 3); + } +} diff --git a/src/test/java/io/jenkins/plugins/audit/listeners/ApiKeyDeletionListenerTest.java b/src/test/java/io/jenkins/plugins/audit/listeners/ApiKeyDeletionListenerTest.java new file mode 100644 index 0000000..93a7aed --- /dev/null +++ b/src/test/java/io/jenkins/plugins/audit/listeners/ApiKeyDeletionListenerTest.java @@ -0,0 +1,141 @@ +package io.jenkins.plugins.audit.listeners; + +/* + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import com.gargoylesoftware.htmlunit.HttpMethod; +import com.gargoylesoftware.htmlunit.Page; +import com.gargoylesoftware.htmlunit.WebRequest; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import hudson.model.User; +import hudson.security.HudsonPrivateSecurityRealm; +import hudson.security.pages.SignupPage; +import jenkins.model.Jenkins; +import jenkins.security.ApiTokenProperty; +import net.sf.json.JSONObject; +import org.acegisecurity.Authentication; +import org.apache.commons.lang.StringUtils; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.apache.logging.log4j.test.appender.ListAppender; +import org.junit.*; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.JenkinsRule.WebClient; +import org.xml.sax.SAXException; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; + +import static org.junit.Assert.*; + +public class ApiKeyDeletionListenerTest { + + private ListAppender app; + private WebClient client; + private final HashMap USERS = new HashMap(); + + private static void assertEventCount(final List events, final int expected) { + assertEquals("Incorrect number of events.", expected, events.size()); + } + + private static WebClient logout(final WebClient wc) throws IOException, SAXException { + wc.goTo("logout"); + return wc; + } + + @Rule + public JenkinsRule j = new JenkinsRule(); + //{j.timeout = 0;} // disable timeouts + + @Before + public void setup() throws Exception { + // user ID conformance check + Field field = HudsonPrivateSecurityRealm.class.getDeclaredField("ID_REGEX"); + field.setAccessible(true); + field.set(null, null); + + // credentials of four Jenkins accounts + USERS.put("alice", "alicePassword"); + USERS.put("bob", "bobPassword"); + USERS.put("charlie", "charliePassword"); + USERS.put("debbie", "debbiePassword"); + + client = j.createWebClient(); + logout(client); + + app = ListAppender.getListAppender("AuditList").clear(); + } + + @After + public void teardown() { + app.clear(); + } + + @Issue("JENKINS-55694") + @Test + public void revokeUserToken() throws Exception { + HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(true, false, null); + j.jenkins.setSecurityRealm(securityRealm); + + List events = app.getEvents(); + assertEventCount(events, 0); + + // new user account creation + SignupPage signup = new SignupPage(client.goTo("signup")); + signup.enterUsername("alice"); + signup.enterPassword("alice"); + signup.enterFullName(StringUtils.capitalize("alice user")); + HtmlPage page = signup.submit(j); + + // execute an http request to delete an existing user api token from their config page + User alice = User.getById("alice", false); + URL configPage = client.createCrumbedUrl(alice.getUrl() + "/" + "/descriptorByName/" + ApiTokenProperty.class.getName() + "/generateNewToken/?newTokenName=" + "alice-token"); + Page p = client.getPage(new WebRequest(configPage, HttpMethod.POST)); + JSONObject responseJson = JSONObject.fromObject(p.getWebResponse().getContentAsString()); + GenerateNewTokenResponse userToken = (GenerateNewTokenResponse) responseJson.getJSONObject("data").toBean(GenerateNewTokenResponse.class); + assertNotNull(userToken.tokenUuid); + + // execute a second http request to delete the just created user api token from their config page + configPage = client.createCrumbedUrl(alice.getUrl() + "/" + "/descriptorByName/" + ApiTokenProperty.class.getName() + "/revoke/?tokenUuid=" + userToken.tokenUuid); + p = client.getPage(new WebRequest(configPage, HttpMethod.POST)); + + // ensure user whose api token was deleted was in fact logged + StructuredDataMessage logMessage = (StructuredDataMessage) events.get(3).getMessage(); + assertTrue(logMessage.toString().contains("deleteKey")); + assertEventCount(events, 4); + } + + /** + * Static class representing the returned api token response + */ + public static class GenerateNewTokenResponse { + public String tokenUuid; + public String tokenName; + public String tokenValue; + } +} diff --git a/src/test/java/io/jenkins/plugins/audit/listeners/UserPasswordLogListenerTest.java b/src/test/java/io/jenkins/plugins/audit/listeners/UserPasswordLogListenerTest.java new file mode 100644 index 0000000..60484b1 --- /dev/null +++ b/src/test/java/io/jenkins/plugins/audit/listeners/UserPasswordLogListenerTest.java @@ -0,0 +1,166 @@ +package io.jenkins.plugins.audit.listeners; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.HashMap; +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.URL; + +import com.gargoylesoftware.htmlunit.WebRequest; +import com.gargoylesoftware.htmlunit.HttpMethod; +import com.gargoylesoftware.htmlunit.Page; +import org.apache.commons.lang.StringUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.After; +import org.junit.Before; +import org.jvnet.hudson.test.Issue; +import org.xml.sax.SAXException; +import org.acegisecurity.Authentication; +import org.jvnet.hudson.test.JenkinsRule; +import org.apache.logging.log4j.core.LogEvent; +import org.jvnet.hudson.test.JenkinsRule.WebClient; +import org.apache.logging.log4j.test.appender.ListAppender; +import org.apache.logging.log4j.message.StructuredDataMessage; + +import com.gargoylesoftware.htmlunit.html.HtmlPage; + +import static org.junit.Assert.*; + +import hudson.model.User; +import jenkins.model.Jenkins; +import hudson.security.pages.SignupPage; +import hudson.security.HudsonPrivateSecurityRealm; + +public class UserPasswordLogListenerTest { + + private ListAppender app; + private WebClient client; + private final HashMap USERS = new HashMap(); + + private static void assertEventCount(final List events, final int expected) { + assertEquals("Incorrect number of events.", expected, events.size()); + } + + private static WebClient logout(final WebClient wc) throws IOException, SAXException { + wc.goTo("logout"); + return wc; + } + + @Rule + public JenkinsRule j = new JenkinsRule(); + //{j.timeout = 0;} // disable timeouts + + @Before + public void setup() throws Exception { + // user ID conformance check + Field field = HudsonPrivateSecurityRealm.class.getDeclaredField("ID_REGEX"); + field.setAccessible(true); + field.set(null, null); + + // credentials of four Jenkins accounts + USERS.put("alice", "alicePassword"); + USERS.put("bob", "bobPassword"); + USERS.put("charlie", "charliePassword"); + USERS.put("debbie", "debbiePassword"); + + client = j.createWebClient(); + logout(client); + + app = ListAppender.getListAppender("AuditList").clear(); + } + + @After + public void teardown() { + app.clear(); + } + + @Issue("JENKINS-55694") + @Test + public void updateUserPassword() throws Exception { + HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(true, false, null); + j.jenkins.setSecurityRealm(securityRealm); + + List events = app.getEvents(); + assertEventCount(events, 0); + + // new user account creation + SignupPage signup = new SignupPage(client.goTo("signup")); + signup.enterUsername("alice"); + signup.enterPassword("alice"); + signup.enterFullName(StringUtils.capitalize("alice user")); + HtmlPage p = signup.submit(j); + + // verify a login event occurred after account creation + client.executeOnServer(() -> { + Authentication a = Jenkins.getAuthentication(); + assertEquals("alice", a.getName()); + + return null; + }); + + // execute an http request to change a user's password from their config page + User alice = User.getById("alice", false); + URL configPage = client.createCrumbedUrl(alice.getUrl() + "/" + "configSubmit"); + String formData = "{\"fullName\": \"alice user\", \"description\": \"\", \"userProperty2\": {\"primaryViewName\": \"\"}, \"userProperty4\": {\"user.password\": \"admin\", \"$redact\": [\"user.password\", \"user.password2\"], \"user.password2\": \"admin\"}, \"userProperty5\": {\"authorizedKeys\": \"\"}, \"userProperty7\": {\"insensitiveSearch\": true}, \"core:apply\": \"true\"}"; + + WebRequest request = new WebRequest(configPage, HttpMethod.POST); + request.setAdditionalHeader("Content-Type", "application/x-www-form-urlencoded"); + request.setRequestBody("json=" + URLEncoder.encode(formData, StandardCharsets.UTF_8.name())); + Page page = client.getPage(request); + + // ensure user whose password was changed was in fact logged + StructuredDataMessage logMessage = (StructuredDataMessage) events.get(2).getMessage(); + assertTrue(logMessage.toString().contains("updatePassword")); + assertEventCount(events, 3); + } + + @Issue("JENKINS-55694") + @Test + public void updateUserPasswordWithDummyRealm() throws Exception { + //HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(true, false, null); + j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); + + List events = app.getEvents(); + assertEventCount(events, 0); + + // new user account creation +// SignupPage signup = new SignupPage(client.goTo("signup")); +// signup.enterUsername("alice"); +// signup.enterPassword("alice"); +// signup.enterFullName(StringUtils.capitalize("alice user")); +// HtmlPage p = signup.submit(j); + + User bob = User.getById("bob", true); + + client.login("bob", "bob"); + + //assertEquals("pin", u.getDisplayName()); + + // verify a login event occurred after account creation + client.executeOnServer(() -> { + Authentication a = Jenkins.getAuthentication(); + assertEquals("bob", a.getName()); + + return null; + }); + + // execute an http request to change a user's password from their config page +// User alice = User.getById("alice", false); + URL configPage = client.createCrumbedUrl(bob.getUrl() + "/" + "configSubmit"); + String formData = "{\"fullName\": \"bob\", \"description\": \"\", \"userProperty2\": {\"primaryViewName\": \"\"}, \"userProperty4\": {\"user.password\": \"admin\", \"$redact\": [\"user.password\", \"user.password2\"], \"user.password2\": \"admin\"}, \"userProperty5\": {\"authorizedKeys\": \"\"}, \"userProperty7\": {\"insensitiveSearch\": true}, \"core:apply\": \"true\"}"; + + WebRequest request = new WebRequest(configPage, HttpMethod.POST); + request.setAdditionalHeader("Content-Type", "application/x-www-form-urlencoded"); + request.setRequestBody("json=" + URLEncoder.encode(formData, StandardCharsets.UTF_8.name())); + Page page = client.getPage(request); +// assertEquals("ok", page.getWebResponse().getContentAsString()); +// +// // ensure user whose password was changed was in fact logged +// StructuredDataMessage logMessage = (StructuredDataMessage) events.get(2).getMessage(); +// assertTrue(logMessage.toString().contains("updatePassword")); +// assertEventCount(events, 3); + } +} From a1557c42a3eea62db07053cb4fa00919a5409d6e Mon Sep 17 00:00:00 2001 From: David Olorundare Date: Mon, 11 Mar 2019 20:53:06 -0500 Subject: [PATCH 3/5] Update pom.xml with listener snapshot-updates --- pom.xml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 417f184..fc20da6 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ 1.0 -SNAPSHOT - 2.161 + 2.169-SNAPSHOT 8 2.11.1 1.0.1 @@ -161,6 +161,10 @@ repo.jenkins-ci.org https://repo.jenkins-ci.org/public/ + + jenkins-2.169-snapshot + https://repo.jenkins-ci.org/snapshots/ + From 0d6044754b5a8ab38fdd79df1fca4255cfdcfdb7 Mon Sep 17 00:00:00 2001 From: David Olorundare Date: Mon, 11 Mar 2019 21:51:31 -0500 Subject: [PATCH 4/5] Add createkey and deletekey events to catalog.json --- src/main/resources/catalog.json | 33 ++++++++---- .../UserPasswordLogListenerTest.java | 53 ------------------- 2 files changed, 23 insertions(+), 63 deletions(-) diff --git a/src/main/resources/catalog.json b/src/main/resources/catalog.json index 01f6ae3..342c325 100644 --- a/src/main/resources/catalog.json +++ b/src/main/resources/catalog.json @@ -16,32 +16,45 @@ } ] }, { "id" : 2, - "name" : "updateKey", - "displayName" : "Update Key", - "description" : "User updates API key(s)", + "name" : "createKey", + "displayName" : "Create Key", + "description" : "User creates a new API key", "aliases" : [ ], "attributes" : [ { "name" : "userId", - "required" : true + "required" : false }, { "name" : "timestamp", - "required" : true + "required" : false } ] }, { "id" : 3, + "name" : "deleteKey", + "displayName" : "Delete Key", + "description" : "User deletes or revokes an existing API key", + "aliases" : [ ], + "attributes" : [ { + "name" : "userId", + "required" : false + }, { + "name" : "timestamp", + "required" : false + } ] + }, { + "id" : 4, "name" : "updatePassword", "displayName" : "Update Password", "description" : "User updates password", "aliases" : [ ], "attributes" : [ { "name" : "userId", - "required" : true + "required" : false }, { "name" : "timestamp", - "required" : true + "required" : false } ] }, { - "id" : 4, + "id" : 5, "name" : "logout", "displayName" : "Logout", "description" : "User Logout", @@ -54,7 +67,7 @@ "required" : false } ] }, { - "id" : 5, + "id" : 6, "name" : "createUser", "displayName" : "Create User", "description" : "Create User", @@ -67,7 +80,7 @@ "required" : false } ] }, { - "id" : 6, + "id" : 7, "name" : "buildStart", "displayName" : "Build Start", "description" : "Start of the Build", diff --git a/src/test/java/io/jenkins/plugins/audit/listeners/UserPasswordLogListenerTest.java b/src/test/java/io/jenkins/plugins/audit/listeners/UserPasswordLogListenerTest.java index 60484b1..af664f1 100644 --- a/src/test/java/io/jenkins/plugins/audit/listeners/UserPasswordLogListenerTest.java +++ b/src/test/java/io/jenkins/plugins/audit/listeners/UserPasswordLogListenerTest.java @@ -51,7 +51,6 @@ private static WebClient logout(final WebClient wc) throws IOException, SAXExcep @Rule public JenkinsRule j = new JenkinsRule(); - //{j.timeout = 0;} // disable timeouts @Before public void setup() throws Exception { @@ -60,12 +59,6 @@ public void setup() throws Exception { field.setAccessible(true); field.set(null, null); - // credentials of four Jenkins accounts - USERS.put("alice", "alicePassword"); - USERS.put("bob", "bobPassword"); - USERS.put("charlie", "charliePassword"); - USERS.put("debbie", "debbiePassword"); - client = j.createWebClient(); logout(client); @@ -117,50 +110,4 @@ public void updateUserPassword() throws Exception { assertEventCount(events, 3); } - @Issue("JENKINS-55694") - @Test - public void updateUserPasswordWithDummyRealm() throws Exception { - //HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(true, false, null); - j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); - - List events = app.getEvents(); - assertEventCount(events, 0); - - // new user account creation -// SignupPage signup = new SignupPage(client.goTo("signup")); -// signup.enterUsername("alice"); -// signup.enterPassword("alice"); -// signup.enterFullName(StringUtils.capitalize("alice user")); -// HtmlPage p = signup.submit(j); - - User bob = User.getById("bob", true); - - client.login("bob", "bob"); - - //assertEquals("pin", u.getDisplayName()); - - // verify a login event occurred after account creation - client.executeOnServer(() -> { - Authentication a = Jenkins.getAuthentication(); - assertEquals("bob", a.getName()); - - return null; - }); - - // execute an http request to change a user's password from their config page -// User alice = User.getById("alice", false); - URL configPage = client.createCrumbedUrl(bob.getUrl() + "/" + "configSubmit"); - String formData = "{\"fullName\": \"bob\", \"description\": \"\", \"userProperty2\": {\"primaryViewName\": \"\"}, \"userProperty4\": {\"user.password\": \"admin\", \"$redact\": [\"user.password\", \"user.password2\"], \"user.password2\": \"admin\"}, \"userProperty5\": {\"authorizedKeys\": \"\"}, \"userProperty7\": {\"insensitiveSearch\": true}, \"core:apply\": \"true\"}"; - - WebRequest request = new WebRequest(configPage, HttpMethod.POST); - request.setAdditionalHeader("Content-Type", "application/x-www-form-urlencoded"); - request.setRequestBody("json=" + URLEncoder.encode(formData, StandardCharsets.UTF_8.name())); - Page page = client.getPage(request); -// assertEquals("ok", page.getWebResponse().getContentAsString()); -// -// // ensure user whose password was changed was in fact logged -// StructuredDataMessage logMessage = (StructuredDataMessage) events.get(2).getMessage(); -// assertTrue(logMessage.toString().contains("updatePassword")); -// assertEventCount(events, 3); - } } From 044ccb37f9d9f49e2a7a70244ddb157f1f13ee1b Mon Sep 17 00:00:00 2001 From: David Olorundare Date: Tue, 12 Mar 2019 06:20:39 -0500 Subject: [PATCH 5/5] Refactor unittest classes --- .../listeners/ApiKeyCreationListenerTest.java | 19 ++++---------- .../listeners/ApiKeyDeletionListenerTest.java | 13 ---------- .../UserPasswordLogListenerTest.java | 26 +++++-------------- 3 files changed, 12 insertions(+), 46 deletions(-) diff --git a/src/test/java/io/jenkins/plugins/audit/listeners/ApiKeyCreationListenerTest.java b/src/test/java/io/jenkins/plugins/audit/listeners/ApiKeyCreationListenerTest.java index 7789271..0eb131f 100644 --- a/src/test/java/io/jenkins/plugins/audit/listeners/ApiKeyCreationListenerTest.java +++ b/src/test/java/io/jenkins/plugins/audit/listeners/ApiKeyCreationListenerTest.java @@ -24,14 +24,6 @@ import hudson.security.HudsonPrivateSecurityRealm; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.HashMap; -import java.io.IOException; -import java.lang.reflect.Field; -import java.net.URL; - import com.gargoylesoftware.htmlunit.WebRequest; import com.gargoylesoftware.htmlunit.HttpMethod; import com.gargoylesoftware.htmlunit.Page; @@ -41,30 +33,30 @@ import org.junit.Test; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.jvnet.hudson.test.Issue; import org.xml.sax.SAXException; -import org.acegisecurity.Authentication; import org.jvnet.hudson.test.JenkinsRule; import org.apache.logging.log4j.core.LogEvent; import org.jvnet.hudson.test.JenkinsRule.WebClient; import org.apache.logging.log4j.test.appender.ListAppender; import org.apache.logging.log4j.message.StructuredDataMessage; +import java.util.List; +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.URL; + import com.gargoylesoftware.htmlunit.html.HtmlPage; import static org.junit.Assert.*; import hudson.model.User; -import jenkins.model.Jenkins; import hudson.security.pages.SignupPage; -import hudson.security.HudsonPrivateSecurityRealm; public class ApiKeyCreationListenerTest { private ListAppender app; private WebClient client; - private final HashMap USERS = new HashMap(); private static void assertEventCount(final List events, final int expected) { assertEquals("Incorrect number of events.", expected, events.size()); @@ -77,7 +69,6 @@ private static WebClient logout(final WebClient wc) throws IOException, SAXExcep @Rule public JenkinsRule j = new JenkinsRule(); - //{j.timeout = 0;} // disable timeouts @Before public void setup() throws Exception { diff --git a/src/test/java/io/jenkins/plugins/audit/listeners/ApiKeyDeletionListenerTest.java b/src/test/java/io/jenkins/plugins/audit/listeners/ApiKeyDeletionListenerTest.java index 93a7aed..5f434ca 100644 --- a/src/test/java/io/jenkins/plugins/audit/listeners/ApiKeyDeletionListenerTest.java +++ b/src/test/java/io/jenkins/plugins/audit/listeners/ApiKeyDeletionListenerTest.java @@ -29,10 +29,8 @@ import hudson.model.User; import hudson.security.HudsonPrivateSecurityRealm; import hudson.security.pages.SignupPage; -import jenkins.model.Jenkins; import jenkins.security.ApiTokenProperty; import net.sf.json.JSONObject; -import org.acegisecurity.Authentication; import org.apache.commons.lang.StringUtils; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.message.StructuredDataMessage; @@ -46,9 +44,6 @@ import java.io.IOException; import java.lang.reflect.Field; import java.net.URL; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; import java.util.List; import static org.junit.Assert.*; @@ -57,7 +52,6 @@ public class ApiKeyDeletionListenerTest { private ListAppender app; private WebClient client; - private final HashMap USERS = new HashMap(); private static void assertEventCount(final List events, final int expected) { assertEquals("Incorrect number of events.", expected, events.size()); @@ -70,7 +64,6 @@ private static WebClient logout(final WebClient wc) throws IOException, SAXExcep @Rule public JenkinsRule j = new JenkinsRule(); - //{j.timeout = 0;} // disable timeouts @Before public void setup() throws Exception { @@ -79,12 +72,6 @@ public void setup() throws Exception { field.setAccessible(true); field.set(null, null); - // credentials of four Jenkins accounts - USERS.put("alice", "alicePassword"); - USERS.put("bob", "bobPassword"); - USERS.put("charlie", "charliePassword"); - USERS.put("debbie", "debbiePassword"); - client = j.createWebClient(); logout(client); diff --git a/src/test/java/io/jenkins/plugins/audit/listeners/UserPasswordLogListenerTest.java b/src/test/java/io/jenkins/plugins/audit/listeners/UserPasswordLogListenerTest.java index af664f1..51fa5aa 100644 --- a/src/test/java/io/jenkins/plugins/audit/listeners/UserPasswordLogListenerTest.java +++ b/src/test/java/io/jenkins/plugins/audit/listeners/UserPasswordLogListenerTest.java @@ -1,13 +1,5 @@ package io.jenkins.plugins.audit.listeners; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.HashMap; -import java.io.IOException; -import java.lang.reflect.Field; -import java.net.URL; - import com.gargoylesoftware.htmlunit.WebRequest; import com.gargoylesoftware.htmlunit.HttpMethod; import com.gargoylesoftware.htmlunit.Page; @@ -18,19 +10,24 @@ import org.junit.Before; import org.jvnet.hudson.test.Issue; import org.xml.sax.SAXException; -import org.acegisecurity.Authentication; import org.jvnet.hudson.test.JenkinsRule; import org.apache.logging.log4j.core.LogEvent; import org.jvnet.hudson.test.JenkinsRule.WebClient; import org.apache.logging.log4j.test.appender.ListAppender; import org.apache.logging.log4j.message.StructuredDataMessage; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.URL; + import com.gargoylesoftware.htmlunit.html.HtmlPage; import static org.junit.Assert.*; import hudson.model.User; -import jenkins.model.Jenkins; import hudson.security.pages.SignupPage; import hudson.security.HudsonPrivateSecurityRealm; @@ -38,7 +35,6 @@ public class UserPasswordLogListenerTest { private ListAppender app; private WebClient client; - private final HashMap USERS = new HashMap(); private static void assertEventCount(final List events, final int expected) { assertEquals("Incorrect number of events.", expected, events.size()); @@ -86,14 +82,6 @@ public void updateUserPassword() throws Exception { signup.enterFullName(StringUtils.capitalize("alice user")); HtmlPage p = signup.submit(j); - // verify a login event occurred after account creation - client.executeOnServer(() -> { - Authentication a = Jenkins.getAuthentication(); - assertEquals("alice", a.getName()); - - return null; - }); - // execute an http request to change a user's password from their config page User alice = User.getById("alice", false); URL configPage = client.createCrumbedUrl(alice.getUrl() + "/" + "configSubmit");