Merge branch 'stable-3.0' into stable-3.1 * stable-3.0: Add autocomplete off to LDAP login form Update git submodules Update git submodules Block the removal of the Realm primary external ids e2e-tests: Replace FileBasedFeederBuilder with FeederBuilder declaration e2e-tests: Add FlushProjectsCache related scenarios Change-Id: Ie52a79c7814bd09a5f1d65387fd20436b8bf13d4
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CheckProjectsCacheFlushEntries.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CheckProjectsCacheFlushEntries.json new file mode 100644 index 0000000..6210deb --- /dev/null +++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CheckProjectsCacheFlushEntries.json
@@ -0,0 +1,6 @@ +[ + { + "url": "http://HOSTNAME:HTTP_PORT/a/config/server/caches/projects", + "entries": "PROJECTS_ENTRIES" + } +]
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/FlushProjectsCache.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/FlushProjectsCache.json new file mode 100644 index 0000000..9ff15a7 --- /dev/null +++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/FlushProjectsCache.json
@@ -0,0 +1,5 @@ +[ + { + "url": "http://HOSTNAME:HTTP_PORT/a/config/server/caches/projects/flush" + } +]
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/GetProjectsCacheEntries.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/GetProjectsCacheEntries.json new file mode 100644 index 0000000..fcf4bc9 --- /dev/null +++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/GetProjectsCacheEntries.json
@@ -0,0 +1,5 @@ +[ + { + "url": "http://HOSTNAME:HTTP_PORT/a/config/server/caches/projects" + } +]
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CacheFlushSimulation.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CacheFlushSimulation.scala new file mode 100644 index 0000000..98d0190 --- /dev/null +++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CacheFlushSimulation.scala
@@ -0,0 +1,32 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.scenarios + +class CacheFlushSimulation extends GerritSimulation { + protected val entriesKey = "entries" + protected val memKey = "mem" + protected var producer: Option[CacheFlushSimulation] = None + protected var consumer: Option[CacheFlushSimulation] = None + + private var cacheEntriesBeforeFlush: Int = 0 + + def entriesBeforeFlush(entries: Int): Unit = { + cacheEntriesBeforeFlush = entries + } + + def expectedEntriesAfterFlush(): Int = { + cacheEntriesBeforeFlush - 1 + } +}
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CheckProjectsCacheFlushEntries.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CheckProjectsCacheFlushEntries.scala new file mode 100644 index 0000000..040e5cf --- /dev/null +++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CheckProjectsCacheFlushEntries.scala
@@ -0,0 +1,48 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.scenarios + +import io.gatling.core.Predef.{atOnceUsers, _} +import io.gatling.core.feeder.FeederBuilder +import io.gatling.core.structure.ScenarioBuilder +import io.gatling.http.Predef._ + +class CheckProjectsCacheFlushEntries extends CacheFlushSimulation { + private val data: FeederBuilder = jsonFile(resource).convert(keys).queue + + def this(producer: CacheFlushSimulation) { + this() + this.producer = Some(producer) + } + + val test: ScenarioBuilder = scenario(unique) + .feed(data) + .exec(session => { + if (producer.nonEmpty) { + session.set(entriesKey, producer.get.expectedEntriesAfterFlush()) + } else { + session + } + }) + .exec(http(unique).get("${url}") + .check(regex("\"" + memKey + "\": (\\d+)") + .is(session => session(entriesKey).as[String]))) + + setUp( + test.inject( + atOnceUsers(1) + ), + ).protocols(httpProtocol) +}
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala index ab91185..c70c393 100644 --- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala +++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala
@@ -15,13 +15,13 @@ package com.google.gerrit.scenarios import io.gatling.core.Predef._ -import io.gatling.core.feeder.FileBasedFeederBuilder +import io.gatling.core.feeder.FeederBuilder import io.gatling.core.structure.ScenarioBuilder import scala.concurrent.duration._ class CloneUsingBothProtocols extends GitSimulation { - private val data: FileBasedFeederBuilder[Any]#F#F = jsonFile(resource).convert(keys).queue + private val data: FeederBuilder = jsonFile(resource).convert(keys).queue private val default: String = name private val duration: Int = 2
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateChange.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateChange.scala index 39b6d42..a5d7358 100644 --- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateChange.scala +++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateChange.scala
@@ -15,14 +15,14 @@ package com.google.gerrit.scenarios import io.gatling.core.Predef.{atOnceUsers, _} -import io.gatling.core.feeder.FileBasedFeederBuilder +import io.gatling.core.feeder.FeederBuilder import io.gatling.core.structure.ScenarioBuilder import io.gatling.http.Predef._ import scala.concurrent.duration._ class CreateChange extends GerritSimulation { - private val data: FileBasedFeederBuilder[Any]#F#F = jsonFile(resource).convert(keys).queue + private val data: FeederBuilder = jsonFile(resource).convert(keys).queue private val default: String = name private val numberKey = "_number"
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateProject.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateProject.scala index 12340a5..3d5e677 100644 --- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateProject.scala +++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateProject.scala
@@ -15,11 +15,11 @@ package com.google.gerrit.scenarios import io.gatling.core.Predef._ -import io.gatling.core.feeder.FileBasedFeederBuilder +import io.gatling.core.feeder.FeederBuilder import io.gatling.core.structure.ScenarioBuilder class CreateProject extends ProjectSimulation { - private val data: FileBasedFeederBuilder[Any]#F#F = jsonFile(resource).convert(keys).queue + private val data: FeederBuilder = jsonFile(resource).convert(keys).queue def this(default: String) { this()
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteChange.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteChange.scala index f3a2d14..a5d3801 100644 --- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteChange.scala +++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteChange.scala
@@ -15,12 +15,12 @@ package com.google.gerrit.scenarios import io.gatling.core.Predef.{atOnceUsers, _} -import io.gatling.core.feeder.FileBasedFeederBuilder +import io.gatling.core.feeder.FeederBuilder import io.gatling.core.structure.ScenarioBuilder import io.gatling.http.Predef.http class DeleteChange extends GerritSimulation { - private val data: FileBasedFeederBuilder[Any]#F#F = jsonFile(resource).convert(keys).queue + private val data: FeederBuilder = jsonFile(resource).convert(keys).queue var number: Option[Int] = None override def relativeRuntimeWeight = 2
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteProject.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteProject.scala index 4b01fd7..983ac0b 100644 --- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteProject.scala +++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteProject.scala
@@ -15,11 +15,11 @@ package com.google.gerrit.scenarios import io.gatling.core.Predef._ -import io.gatling.core.feeder.FileBasedFeederBuilder +import io.gatling.core.feeder.FeederBuilder import io.gatling.core.structure.ScenarioBuilder class DeleteProject extends ProjectSimulation { - private val data: FileBasedFeederBuilder[Any]#F#F = jsonFile(resource).convert(keys).queue + private val data: FeederBuilder = jsonFile(resource).convert(keys).queue def this(default: String) { this()
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/FlushProjectsCache.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/FlushProjectsCache.scala new file mode 100644 index 0000000..94f4ae3 --- /dev/null +++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/FlushProjectsCache.scala
@@ -0,0 +1,60 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.scenarios + +import io.gatling.core.Predef._ +import io.gatling.core.feeder.FeederBuilder +import io.gatling.core.structure.ScenarioBuilder + +import scala.concurrent.duration._ + +class FlushProjectsCache extends CacheFlushSimulation { + private val data: FeederBuilder = jsonFile(resource).convert(keys).queue + private val default: String = name + + override def relativeRuntimeWeight = 2 + + private val flushCache: ScenarioBuilder = scenario(unique) + .feed(data) + .exec(httpRequest) + + private val createProject = new CreateProject(default) + private val getCacheEntriesAfterProject = new GetProjectsCacheEntries(this) + private val checkCacheEntriesAfterFlush = new CheckProjectsCacheFlushEntries(this) + private val deleteProject = new DeleteProject(default) + + setUp( + createProject.test.inject( + nothingFor(stepWaitTime(createProject) seconds), + atOnceUsers(1) + ), + getCacheEntriesAfterProject.test.inject( + nothingFor(stepWaitTime(getCacheEntriesAfterProject) seconds), + atOnceUsers(1) + ), + flushCache.inject( + nothingFor(stepWaitTime(this) seconds), + atOnceUsers(1) + ), + checkCacheEntriesAfterFlush.test.inject( + nothingFor(stepWaitTime(checkCacheEntriesAfterFlush) seconds), + atOnceUsers(1) + ), + deleteProject.test.inject( + nothingFor(stepWaitTime(deleteProject) seconds), + atOnceUsers(1) + ), + ).protocols(httpProtocol) +}
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GerritSimulation.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GerritSimulation.scala index 36df627..fb79f80 100644 --- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GerritSimulation.scala +++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GerritSimulation.scala
@@ -68,6 +68,8 @@ case ("project", project) => val precedes = replaceKeyWith("_project", name, project.toString) replaceProperty("project", precedes) + case ("entries", entries) => + replaceProperty("projects_entries", "1", entries.toString) } private def replaceProperty(term: String, in: String): String = {
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GetProjectsCacheEntries.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GetProjectsCacheEntries.scala new file mode 100644 index 0000000..27e3f19 --- /dev/null +++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GetProjectsCacheEntries.scala
@@ -0,0 +1,45 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.scenarios + +import io.gatling.core.Predef._ +import io.gatling.core.feeder.FeederBuilder +import io.gatling.core.structure.ScenarioBuilder +import io.gatling.http.Predef.{http, _} + +class GetProjectsCacheEntries extends CacheFlushSimulation { + private val data: FeederBuilder = jsonFile(resource).convert(keys).queue + + def this(consumer: CacheFlushSimulation) { + this() + this.consumer = Some(consumer) + } + + val test: ScenarioBuilder = scenario(unique) + .feed(data) + .exec(http(unique).get("${url}") + .check(regex("\"" + memKey + "\": (\\d+)").saveAs(entriesKey))) + .exec(session => { + if (consumer.nonEmpty) { + consumer.get.entriesBeforeFlush(session(entriesKey).as[Int]) + } + session + }) + + setUp( + test.inject( + atOnceUsers(1) + )).protocols(httpProtocol) +}
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala index 74dc052..e5b41b3 100644 --- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala +++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala
@@ -15,13 +15,13 @@ package com.google.gerrit.scenarios import io.gatling.core.Predef._ -import io.gatling.core.feeder.FileBasedFeederBuilder +import io.gatling.core.feeder.FeederBuilder import io.gatling.core.structure.ScenarioBuilder import scala.concurrent.duration._ class ReplayRecordsFromFeeder extends GitSimulation { - private val data: FileBasedFeederBuilder[Any]#F#F = jsonFile(resource).convert(keys).circular + private val data: FeederBuilder = jsonFile(resource).convert(keys).circular private val default: String = name override def relativeRuntimeWeight = 30
diff --git a/java/com/google/gerrit/server/restapi/account/DeleteEmail.java b/java/com/google/gerrit/server/restapi/account/DeleteEmail.java index f690acb..5cdf4ae 100644 --- a/java/com/google/gerrit/server/restapi/account/DeleteEmail.java +++ b/java/com/google/gerrit/server/restapi/account/DeleteEmail.java
@@ -18,6 +18,7 @@ import com.google.gerrit.entities.Account; import com.google.gerrit.extensions.client.AccountFieldName; +import com.google.gerrit.extensions.client.AuthType; import com.google.gerrit.extensions.common.Input; import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.MethodNotAllowedException; @@ -33,6 +34,7 @@ import com.google.gerrit.server.account.Realm; import com.google.gerrit.server.account.externalids.ExternalId; import com.google.gerrit.server.account.externalids.ExternalIds; +import com.google.gerrit.server.config.AuthConfig; import com.google.gerrit.server.permissions.GlobalPermission; import com.google.gerrit.server.permissions.PermissionBackend; import com.google.gerrit.server.permissions.PermissionBackendException; @@ -51,6 +53,7 @@ private final PermissionBackend permissionBackend; private final AccountManager accountManager; private final ExternalIds externalIds; + private final AuthType authType; @Inject DeleteEmail( @@ -58,12 +61,14 @@ Realm realm, PermissionBackend permissionBackend, AccountManager accountManager, - ExternalIds externalIds) { + ExternalIds externalIds, + AuthConfig authConfig) { this.self = self; this.realm = realm; this.permissionBackend = permissionBackend; this.accountManager = accountManager; this.externalIds = externalIds; + this.authType = authConfig.getAuthType(); } @Override @@ -94,6 +99,14 @@ throw new ResourceNotFoundException(email); } + if (realm.accountBelongsToRealm(extIds)) { + String errorMsg = + String.format( + "Cannot remove e-mail '%s' which is directly associated with %s authentication", + email, authType); + throw new ResourceConflictException(errorMsg); + } + try { accountManager.unlink( user.getAccountId(), extIds.stream().map(ExternalId::key).collect(toSet()));
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java index 8c82498..ac00782 100644 --- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java +++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -1356,6 +1356,74 @@ } @Test + @GerritConfig(name = "auth.type", value = "LDAP") + public void deleteEmailShouldNotRemoveLdapExternalIdScheme() throws Exception { + String ldapEmail = "foo@company.com"; + String ldapExternalId = ExternalId.SCHEME_GERRIT + ":foo"; + accountsUpdateProvider + .get() + .update( + "Add External IDs", + admin.id(), + u -> + u.addExternalId( + ExternalId.createWithEmail( + ExternalId.Key.parse(ldapExternalId), admin.id(), ldapEmail))); + assertThat( + gApi.accounts().self().getExternalIds().stream().map(e -> e.identity).collect(toSet())) + .contains(ldapExternalId); + + requestScopeOperations.resetCurrentApiUser(); + assertThat(getEmails()).contains(ldapEmail); + + ResourceConflictException exception = + assertThrows( + ResourceConflictException.class, () -> gApi.accounts().self().deleteEmail(ldapEmail)); + assertThat(exception).hasMessageThat().contains(ldapEmail); + + requestScopeOperations.resetCurrentApiUser(); + assertThat(getEmails()).contains(ldapEmail); + assertThat( + gApi.accounts().self().getExternalIds().stream().map(e -> e.identity).collect(toSet())) + .contains(ldapExternalId); + } + + @Test + @GerritConfig(name = "auth.type", value = "LDAP") + public void deleteEmailShouldRemoveNonLdapExternalIdScheme() throws Exception { + String ldapEmail = "foo@company.com"; + String ldapExternalId = ExternalId.SCHEME_GERRIT + ":foo"; + String nonLdapEMail = "foo@example.com"; + String nonLdapExternalId = ExternalId.SCHEME_MAILTO + ":foo@example.com"; + accountsUpdateProvider + .get() + .update( + "Add External IDs", + admin.id(), + u -> + u.addExternalId( + ExternalId.createWithEmail( + ExternalId.Key.parse(nonLdapExternalId), admin.id(), nonLdapEMail)) + .addExternalId( + ExternalId.createWithEmail( + ExternalId.Key.parse(ldapExternalId), admin.id(), ldapEmail))); + assertThat( + gApi.accounts().self().getExternalIds().stream().map(e -> e.identity).collect(toSet())) + .containsAtLeast(ldapExternalId, nonLdapExternalId); + + requestScopeOperations.resetCurrentApiUser(); + assertThat(getEmails()).containsAtLeast(ldapEmail, nonLdapEMail); + + gApi.accounts().self().deleteEmail(nonLdapEMail); + + requestScopeOperations.resetCurrentApiUser(); + assertThat(getEmails()).doesNotContain(nonLdapEMail); + assertThat( + gApi.accounts().self().getExternalIds().stream().map(e -> e.identity).collect(toSet())) + .contains(ldapExternalId); + } + + @Test public void deleteEmailOfOtherUser() throws Exception { AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter(); try (Registration registration =
diff --git a/resources/com/google/gerrit/httpd/auth/ldap/LoginForm.html b/resources/com/google/gerrit/httpd/auth/ldap/LoginForm.html index 39900e8..593ea45 100644 --- a/resources/com/google/gerrit/httpd/auth/ldap/LoginForm.html +++ b/resources/com/google/gerrit/httpd/auth/ldap/LoginForm.html
@@ -21,7 +21,7 @@ <div id="gerrit_body" class="gerritBody"> <h1>Sign In to Gerrit Code Review at <span id="hostName">example.com</span></h1> <div id="error_message">Invalid username or password.</div> - <form method="POST" action="#" id="login_form" onsubmit="return shouldSubmit()"> + <form method="POST" action="#" id="login_form" autocomplete="off" onsubmit="return shouldSubmit()"> <table style="border: 0;"> <tr> <th>Username</th>