mirror of
https://github.com/google/nomulus
synced 2026-01-16 02:33:16 +00:00
Compare commits
1 Commits
proxy-2025
...
test
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a67b04f3a |
@@ -77,17 +77,4 @@
|
||||
Save
|
||||
</button>
|
||||
</form>
|
||||
@if(userDataService.userData()?.isAdmin) {
|
||||
<div class="settings-security__reset-password-field">
|
||||
<h2>Need to reset your EPP password?</h2>
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
aria-label="Reset EPP password via email"
|
||||
(click)="requestEppPasswordReset()"
|
||||
>
|
||||
Reset EPP password via email
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -1,35 +1,16 @@
|
||||
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
.settings-security {
|
||||
&__edit-password {
|
||||
max-width: 616px;
|
||||
&-field {
|
||||
.settings-security__edit-password {
|
||||
max-width: 616px;
|
||||
&-field {
|
||||
width: 100%;
|
||||
mat-form-field {
|
||||
margin-bottom: 20px;
|
||||
width: 100%;
|
||||
mat-form-field {
|
||||
margin-bottom: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
&-form {
|
||||
margin-top: 30px;
|
||||
}
|
||||
&-save {
|
||||
margin-top: 30px;
|
||||
}
|
||||
}
|
||||
&__reset-password-field {
|
||||
margin-top: 60px;
|
||||
&-form {
|
||||
margin-top: 30px;
|
||||
}
|
||||
&-save {
|
||||
margin-top: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,43 +24,11 @@ import {
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { RegistrarService } from 'src/app/registrar/registrar.service';
|
||||
import { SecurityService } from './security.service';
|
||||
import { UserDataService } from 'src/app/shared/services/userData.service';
|
||||
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MaterialModule } from 'src/app/material.module';
|
||||
import { filter, switchMap, take } from 'rxjs';
|
||||
import { BackendService } from 'src/app/shared/services/backend.service';
|
||||
|
||||
type errorCode = 'required' | 'maxlength' | 'minlength' | 'passwordsDontMatch';
|
||||
|
||||
type errorFriendlyText = { [type in errorCode]: String };
|
||||
|
||||
@Component({
|
||||
selector: 'app-reset-epp-password-dialog',
|
||||
template: `
|
||||
<h2 mat-dialog-title>Please confirm the password reset:</h2>
|
||||
<mat-dialog-content>
|
||||
This will send an EPP password reset email to the admin POC.
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<button mat-button (click)="onCancel()">Cancel</button>
|
||||
<button mat-button color="warn" (click)="onSave()">Confirm</button>
|
||||
</mat-dialog-actions>
|
||||
`,
|
||||
imports: [CommonModule, MaterialModule],
|
||||
})
|
||||
export class ResetEppPasswordComponent {
|
||||
constructor(public dialogRef: MatDialogRef<ResetEppPasswordComponent>) {}
|
||||
|
||||
onSave(): void {
|
||||
this.dialogRef.close(true);
|
||||
}
|
||||
|
||||
onCancel(): void {
|
||||
this.dialogRef.close(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-epp-password-edit',
|
||||
templateUrl: './eppPasswordEdit.component.html',
|
||||
@@ -80,12 +48,9 @@ export default class EppPasswordEditComponent {
|
||||
};
|
||||
|
||||
constructor(
|
||||
public registrarService: RegistrarService,
|
||||
public securityService: SecurityService,
|
||||
protected userDataService: UserDataService,
|
||||
private backendService: BackendService,
|
||||
private resetPasswordDialog: MatDialog,
|
||||
private _snackBar: MatSnackBar
|
||||
private _snackBar: MatSnackBar,
|
||||
public registrarService: RegistrarService
|
||||
) {}
|
||||
|
||||
hasError(controlName: string) {
|
||||
@@ -155,26 +120,4 @@ export default class EppPasswordEditComponent {
|
||||
goBack() {
|
||||
this.securityService.isEditingPassword = false;
|
||||
}
|
||||
|
||||
sendEppPasswordResetRequest() {
|
||||
return this.backendService.requestEppPasswordReset(
|
||||
this.registrarService.registrarId()
|
||||
);
|
||||
}
|
||||
|
||||
requestEppPasswordReset() {
|
||||
const dialogRef = this.resetPasswordDialog.open(ResetEppPasswordComponent);
|
||||
dialogRef
|
||||
.afterClosed()
|
||||
.pipe(
|
||||
take(1),
|
||||
filter((result) => !!result)
|
||||
)
|
||||
.pipe(switchMap((_) => this.sendEppPasswordResetRequest()))
|
||||
.subscribe({
|
||||
next: (_) => this.goBack(),
|
||||
error: (err: HttpErrorResponse) =>
|
||||
this._snackBar.open(err.error || err.message),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,11 +291,4 @@ export class BackendService {
|
||||
registryLockEmail,
|
||||
});
|
||||
}
|
||||
|
||||
requestEppPasswordReset(registrarId: string) {
|
||||
return this.http.post('/console-api/password-reset-request', {
|
||||
type: 'EPP',
|
||||
registrarId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import java.util.Optional
|
||||
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id "org.flywaydb.flyway" version "11.0.1"
|
||||
id "org.flywaydb.flyway" version "9.22.3"
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import static google.registry.xml.ValidationMode.STRICT;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.flows.EppException.CommandUseErrorException;
|
||||
import google.registry.flows.EppException.ParameterValueRangeErrorException;
|
||||
@@ -31,7 +30,6 @@ import google.registry.flows.custom.EntityChanges;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.adapters.CurrencyUnitAdapter.UnknownCurrencyException;
|
||||
import google.registry.model.eppcommon.EppXmlTransformer;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppinput.EppInput.WrongProtocolVersionException;
|
||||
import google.registry.model.eppoutput.EppOutput;
|
||||
import google.registry.model.host.InetAddressAdapter.IpVersionMismatchException;
|
||||
@@ -42,9 +40,6 @@ import java.util.List;
|
||||
/** Static utility functions for flows. */
|
||||
public final class FlowUtils {
|
||||
|
||||
public static final ImmutableSet<StatusValue> DELETE_PROHIBITED_STATUSES =
|
||||
ImmutableSet.of(StatusValue.CLIENT_DELETE_PROHIBITED, StatusValue.SERVER_DELETE_PROHIBITED);
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private FlowUtils() {}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static google.registry.flows.FlowUtils.DELETE_PROHIBITED_STATUSES;
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.checkLinkedDomains;
|
||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
@@ -66,6 +65,12 @@ import org.joda.time.DateTime;
|
||||
@ReportingSpec(ActivityReportField.CONTACT_DELETE)
|
||||
public final class ContactDeleteFlow implements MutatingFlow {
|
||||
|
||||
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES =
|
||||
ImmutableSet.of(
|
||||
StatusValue.CLIENT_DELETE_PROHIBITED,
|
||||
StatusValue.PENDING_DELETE,
|
||||
StatusValue.SERVER_DELETE_PROHIBITED);
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject @RegistrarId String registrarId;
|
||||
@Inject @TargetId String targetId;
|
||||
@@ -86,10 +91,9 @@ public final class ContactDeleteFlow implements MutatingFlow {
|
||||
DateTime now = tm().getTransactionTime();
|
||||
checkLinkedDomains(targetId, now, Contact.class);
|
||||
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
|
||||
verifyNoDisallowedStatuses(existingContact, DISALLOWED_STATUSES);
|
||||
verifyOptionalAuthInfo(authInfo, existingContact);
|
||||
verifyNoDisallowedStatuses(existingContact, ImmutableSet.of(StatusValue.PENDING_DELETE));
|
||||
if (!isSuperuser) {
|
||||
verifyNoDisallowedStatuses(existingContact, DELETE_PROHIBITED_STATUSES);
|
||||
verifyResourceOwnership(registrarId, existingContact);
|
||||
}
|
||||
// Handle pending transfers on contact deletion.
|
||||
|
||||
@@ -17,7 +17,6 @@ package google.registry.flows.domain;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static google.registry.dns.DnsUtils.requestDomainDnsRefresh;
|
||||
import static google.registry.flows.FlowUtils.DELETE_PROHIBITED_STATUSES;
|
||||
import static google.registry.flows.FlowUtils.createHistoryEntryId;
|
||||
import static google.registry.flows.FlowUtils.persistEntityChanges;
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
@@ -123,6 +122,11 @@ public final class DomainDeleteFlow implements MutatingFlow, SqlStatementLogging
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES = ImmutableSet.of(
|
||||
StatusValue.CLIENT_DELETE_PROHIBITED,
|
||||
StatusValue.PENDING_DELETE,
|
||||
StatusValue.SERVER_DELETE_PROHIBITED);
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject EppInput eppInput;
|
||||
@Inject SessionMetadata sessionMetadata;
|
||||
@@ -300,10 +304,9 @@ public final class DomainDeleteFlow implements MutatingFlow, SqlStatementLogging
|
||||
|
||||
private void verifyDeleteAllowed(Domain existingDomain, Tld tld, DateTime now)
|
||||
throws EppException {
|
||||
verifyNoDisallowedStatuses(existingDomain, DISALLOWED_STATUSES);
|
||||
verifyOptionalAuthInfo(authInfo, existingDomain);
|
||||
verifyNoDisallowedStatuses(existingDomain, ImmutableSet.of(StatusValue.PENDING_DELETE));
|
||||
if (!isSuperuser) {
|
||||
verifyNoDisallowedStatuses(existingDomain, DELETE_PROHIBITED_STATUSES);
|
||||
verifyResourceOwnership(registrarId, existingDomain);
|
||||
verifyNotInPredelegation(tld, now);
|
||||
checkAllowedAccessToTld(registrarId, tld.getTld().toString());
|
||||
|
||||
@@ -481,10 +481,10 @@ public class DomainFlowUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforces the presence/absence of contact data on domain creates depending on the minimum data
|
||||
* set migration schedule.
|
||||
* Enforces the presence/absence of contact data depending on the minimum data set migration
|
||||
* schedule.
|
||||
*/
|
||||
static void validateCreateContactData(
|
||||
static void validateContactDataPresence(
|
||||
Optional<VKey<Contact>> registrant, Set<DesignatedContact> contacts)
|
||||
throws RequiredParameterMissingException, ParameterValuePolicyErrorException {
|
||||
// TODO(b/353347632): Change these flag checks to a registry config check once minimum data set
|
||||
@@ -514,45 +514,6 @@ public class DomainFlowUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforces the presence/absence of contact data on domain updates depending on the minimum data
|
||||
* set migration schedule.
|
||||
*/
|
||||
static void validateUpdateContactData(
|
||||
Optional<VKey<Contact>> existingRegistrant,
|
||||
Optional<VKey<Contact>> newRegistrant,
|
||||
Set<DesignatedContact> existingContacts,
|
||||
Set<DesignatedContact> newContacts)
|
||||
throws RequiredParameterMissingException, ParameterValuePolicyErrorException {
|
||||
// TODO(b/353347632): Change these flag checks to a registry config check once minimum data set
|
||||
// migration is completed.
|
||||
if (FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_PROHIBITED)) {
|
||||
// Throw if the update specifies a new registrant that is different from the existing one.
|
||||
if (newRegistrant.isPresent() && !newRegistrant.equals(existingRegistrant)) {
|
||||
throw new RegistrantProhibitedException();
|
||||
}
|
||||
// Throw if the update specifies any new contacts that weren't already present on the domain.
|
||||
if (!Sets.difference(newContacts, existingContacts).isEmpty()) {
|
||||
throw new ContactsProhibitedException();
|
||||
}
|
||||
} else if (!FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_OPTIONAL)) {
|
||||
// Throw if the update empties out a registrant that had been present.
|
||||
if (newRegistrant.isEmpty() && existingRegistrant.isPresent()) {
|
||||
throw new MissingRegistrantException();
|
||||
}
|
||||
// Throw if the update contains no admin contact when one had been present.
|
||||
if (existingContacts.stream().anyMatch(c -> c.getType().equals(Type.ADMIN))
|
||||
&& newContacts.stream().noneMatch(c -> c.getType().equals(Type.ADMIN))) {
|
||||
throw new MissingAdminContactException();
|
||||
}
|
||||
// Throw if the update contains no tech contact when one had been present.
|
||||
if (existingContacts.stream().anyMatch(c -> c.getType().equals(Type.TECH))
|
||||
&& newContacts.stream().noneMatch(c -> c.getType().equals(Type.TECH))) {
|
||||
throw new MissingTechnicalContactException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void validateRegistrantAllowedOnTld(String tld, Optional<String> registrantContactId)
|
||||
throws RegistrantNotAllowedException {
|
||||
ImmutableSet<String> allowedRegistrants = Tld.get(tld).getAllowedRegistrantContactIds();
|
||||
@@ -1093,7 +1054,7 @@ public class DomainFlowUtils {
|
||||
String tldStr = tld.getTldStr();
|
||||
validateRegistrantAllowedOnTld(tldStr, command.getRegistrantContactId());
|
||||
validateNoDuplicateContacts(command.getContacts());
|
||||
validateCreateContactData(command.getRegistrant(), command.getContacts());
|
||||
validateContactDataPresence(command.getRegistrant(), command.getContacts());
|
||||
ImmutableSet<String> hostNames = command.getNameserverHostNames();
|
||||
validateNameserversCountForTld(tldStr, domainName, hostNames.size());
|
||||
validateNameserversAllowedOnTld(tldStr, hostNames);
|
||||
|
||||
@@ -30,6 +30,7 @@ import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.cloneAndLinkReferences;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.updateDsData;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.validateContactDataPresence;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.validateContactsHaveTypes;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.validateDsData;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.validateFeesAckedIfPresent;
|
||||
@@ -37,7 +38,6 @@ import static google.registry.flows.domain.DomainFlowUtils.validateNameserversAl
|
||||
import static google.registry.flows.domain.DomainFlowUtils.validateNameserversCountForTld;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.validateNoDuplicateContacts;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.validateRegistrantAllowedOnTld;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.validateUpdateContactData;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.verifyClientUpdateNotProhibited;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.verifyNotInPendingDelete;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_OPTIONAL;
|
||||
@@ -186,7 +186,7 @@ public final class DomainUpdateFlow implements MutatingFlow {
|
||||
Domain newDomain = performUpdate(command, existingDomain, now);
|
||||
DomainHistory domainHistory =
|
||||
historyBuilder.setType(DOMAIN_UPDATE).setDomain(newDomain).build();
|
||||
validateNewState(existingDomain, newDomain);
|
||||
validateNewState(newDomain);
|
||||
if (requiresDnsUpdate(existingDomain, newDomain)) {
|
||||
requestDomainDnsRefresh(targetId);
|
||||
}
|
||||
@@ -328,13 +328,8 @@ public final class DomainUpdateFlow implements MutatingFlow {
|
||||
* compliant with the additions or amendments, otherwise existing data can become invalid and
|
||||
* cause Domain update failure.
|
||||
*/
|
||||
private static void validateNewState(Domain existingDomain, Domain newDomain)
|
||||
throws EppException {
|
||||
validateUpdateContactData(
|
||||
existingDomain.getRegistrant(),
|
||||
newDomain.getRegistrant(),
|
||||
existingDomain.getContacts(),
|
||||
newDomain.getContacts());
|
||||
private static void validateNewState(Domain newDomain) throws EppException {
|
||||
validateContactDataPresence(newDomain.getRegistrant(), newDomain.getContacts());
|
||||
validateDsData(newDomain.getDsData());
|
||||
validateNameserversCountForTld(
|
||||
newDomain.getTld(),
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
package google.registry.flows.host;
|
||||
|
||||
import static google.registry.dns.DnsUtils.requestHostDnsRefresh;
|
||||
import static google.registry.flows.FlowUtils.DELETE_PROHIBITED_STATUSES;
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.checkLinkedDomains;
|
||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
@@ -66,6 +65,12 @@ import org.joda.time.DateTime;
|
||||
@ReportingSpec(ActivityReportField.HOST_DELETE)
|
||||
public final class HostDeleteFlow implements MutatingFlow {
|
||||
|
||||
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES =
|
||||
ImmutableSet.of(
|
||||
StatusValue.CLIENT_DELETE_PROHIBITED,
|
||||
StatusValue.PENDING_DELETE,
|
||||
StatusValue.SERVER_DELETE_PROHIBITED);
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject @RegistrarId String registrarId;
|
||||
@Inject @TargetId String targetId;
|
||||
@@ -86,9 +91,8 @@ public final class HostDeleteFlow implements MutatingFlow {
|
||||
validateHostName(targetId);
|
||||
checkLinkedDomains(targetId, now, Host.class);
|
||||
Host existingHost = loadAndVerifyExistence(Host.class, targetId, now);
|
||||
verifyNoDisallowedStatuses(existingHost, ImmutableSet.of(StatusValue.PENDING_DELETE));
|
||||
verifyNoDisallowedStatuses(existingHost, DISALLOWED_STATUSES);
|
||||
if (!isSuperuser) {
|
||||
verifyNoDisallowedStatuses(existingHost, DELETE_PROHIBITED_STATUSES);
|
||||
// Hosts transfer with their superordinate domains, so for hosts with a superordinate domain,
|
||||
// the client id, needs to be read off of it.
|
||||
EppResource owningResource =
|
||||
|
||||
@@ -95,4 +95,10 @@ public class SignedMarkRevocationList extends ImmutableObject {
|
||||
public int size() {
|
||||
return revokes.size();
|
||||
}
|
||||
|
||||
/** Save this list to Cloud SQL. Returns {@code this}. */
|
||||
public SignedMarkRevocationList save() {
|
||||
SignedMarkRevocationListDao.save(this);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,25 +44,11 @@ public class SignedMarkRevocationListDao {
|
||||
return smdrl.orElseGet(() -> SignedMarkRevocationList.create(START_OF_TIME, ImmutableMap.of()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists a {@link SignedMarkRevocationList} instance and returns the persisted entity.
|
||||
*
|
||||
* <p>Note that the input parameter is untouched. Use the returned object if metadata fields like
|
||||
* {@code revisionId} are needed.
|
||||
*/
|
||||
public static SignedMarkRevocationList save(SignedMarkRevocationList signedMarkRevocationList) {
|
||||
var persisted =
|
||||
tm().transact(
|
||||
() -> {
|
||||
var entity =
|
||||
SignedMarkRevocationList.create(
|
||||
signedMarkRevocationList.getCreationTime(),
|
||||
ImmutableMap.copyOf(signedMarkRevocationList.revokes));
|
||||
tm().insert(entity);
|
||||
return entity;
|
||||
});
|
||||
/** Save the given {@link SignedMarkRevocationList} */
|
||||
static void save(SignedMarkRevocationList signedMarkRevocationList) {
|
||||
tm().transact(() -> tm().insert(signedMarkRevocationList));
|
||||
logger.atInfo().log(
|
||||
"Inserted %,d signed mark revocations into Cloud SQL.", persisted.revokes.size());
|
||||
return persisted;
|
||||
"Inserted %,d signed mark revocations into Cloud SQL.",
|
||||
signedMarkRevocationList.revokes.size());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,30 +133,24 @@ public final class PremiumListDao {
|
||||
}
|
||||
|
||||
/** Saves the given premium list (and its premium list entries) to Cloud SQL. */
|
||||
public static PremiumList save(PremiumList premiumListToPersist) {
|
||||
PremiumList persisted =
|
||||
tm().transact(
|
||||
() -> {
|
||||
// Make a new copy in each attempt to insert. See javadoc of the insert method for
|
||||
// more information.
|
||||
PremiumList premiumList = premiumListToPersist.asBuilder().build();
|
||||
tm().insert(premiumList);
|
||||
tm().getEntityManager().flush(); // This populates the revisionId.
|
||||
long revisionId = premiumList.getRevisionId();
|
||||
public static PremiumList save(PremiumList premiumList) {
|
||||
tm().transact(
|
||||
() -> {
|
||||
tm().insert(premiumList);
|
||||
tm().getEntityManager().flush(); // This populates the revisionId.
|
||||
long revisionId = premiumList.getRevisionId();
|
||||
|
||||
if (!isNullOrEmpty(premiumList.getLabelsToPrices())) {
|
||||
ImmutableSet.Builder<PremiumEntry> entries = new ImmutableSet.Builder<>();
|
||||
premiumList
|
||||
.getLabelsToPrices()
|
||||
.forEach(
|
||||
(key, value) ->
|
||||
entries.add(PremiumEntry.create(revisionId, value, key)));
|
||||
tm().insertAll(entries.build());
|
||||
}
|
||||
return premiumList;
|
||||
});
|
||||
premiumListCache.invalidate(persisted.getName());
|
||||
return persisted;
|
||||
if (!isNullOrEmpty(premiumList.getLabelsToPrices())) {
|
||||
ImmutableSet.Builder<PremiumEntry> entries = new ImmutableSet.Builder<>();
|
||||
premiumList
|
||||
.getLabelsToPrices()
|
||||
.forEach(
|
||||
(key, value) -> entries.add(PremiumEntry.create(revisionId, value, key)));
|
||||
tm().insertAll(entries.build());
|
||||
}
|
||||
});
|
||||
premiumListCache.invalidate(premiumList.getName());
|
||||
return premiumList;
|
||||
}
|
||||
|
||||
public static void delete(PremiumList premiumList) {
|
||||
|
||||
@@ -27,26 +27,14 @@ public class ReservedListDao {
|
||||
|
||||
private ReservedListDao() {}
|
||||
|
||||
/**
|
||||
* Persists a new reserved list to Cloud SQL and returns the persisted entity.
|
||||
*
|
||||
* <p>Note that the input parameter is untouched. Use the returned object if metadata fields like
|
||||
* {@code revisionId} are needed.
|
||||
*/
|
||||
public static ReservedList save(ReservedList reservedList) {
|
||||
/** Persist a new reserved list to Cloud SQL. */
|
||||
public static void save(ReservedList reservedList) {
|
||||
checkArgumentNotNull(reservedList, "Must specify reservedList");
|
||||
logger.atInfo().log("Saving reserved list %s to Cloud SQL.", reservedList.getName());
|
||||
var persisted =
|
||||
tm().transact(
|
||||
() -> {
|
||||
var entity = reservedList.asBuilder().build();
|
||||
tm().insert(entity);
|
||||
return entity;
|
||||
});
|
||||
tm().transact(() -> tm().insert(reservedList));
|
||||
logger.atInfo().log(
|
||||
"Saved reserved list %s with %d entries to Cloud SQL.",
|
||||
reservedList.getName(), reservedList.getReservedListEntries().size());
|
||||
return persisted;
|
||||
}
|
||||
|
||||
/** Deletes a reserved list from Cloud SQL. */
|
||||
|
||||
@@ -49,23 +49,10 @@ public class ClaimsListDao {
|
||||
return CacheUtils.newCacheBuilder(expiry).build(ignored -> ClaimsListDao.getUncached());
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists a {@link ClaimsList} instance and returns the persisted entity.
|
||||
*
|
||||
* <p>Note that the input parameter is untouched. Use the returned object if metadata fields like
|
||||
* {@code revisionId} are needed.
|
||||
*/
|
||||
public static ClaimsList save(ClaimsList claimsList) {
|
||||
var persisted =
|
||||
tm().transact(
|
||||
() -> {
|
||||
var entity =
|
||||
ClaimsList.create(claimsList.tmdbGenerationTime, claimsList.labelsToKeys);
|
||||
tm().insert(entity);
|
||||
return entity;
|
||||
});
|
||||
CACHE.put(ClaimsListDao.class, persisted);
|
||||
return persisted;
|
||||
/** Saves the given {@link ClaimsList} to Cloud SQL. */
|
||||
public static void save(ClaimsList claimsList) {
|
||||
tm().transact(() -> tm().insert(claimsList));
|
||||
CACHE.put(ClaimsListDao.class, claimsList);
|
||||
}
|
||||
|
||||
/** Returns the most recent revision of the {@link ClaimsList} from the cache. */
|
||||
|
||||
@@ -344,18 +344,6 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
return txnInfo.transactionTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts an object into the database.
|
||||
*
|
||||
* <p>If {@code entity} has an auto-generated identity field (i.e., a field annotated with {@link
|
||||
* jakarta.persistence.GeneratedValue}), the caller must not assign a value to this field,
|
||||
* otherwise Hibernate would mistake the entity as detached and raise an error.
|
||||
*
|
||||
* <p>The practical implication of the above is that when inserting such an entity using a
|
||||
* retriable transaction , the entity should be instantiated inside the transaction body. A failed
|
||||
* attempt may still assign and ID to the entity, therefore reusing the same entity would cause
|
||||
* retries to fail.
|
||||
*/
|
||||
@Override
|
||||
public void insert(Object entity) {
|
||||
checkArgumentNotNull(entity, "entity must be specified");
|
||||
|
||||
@@ -18,12 +18,11 @@ import static com.google.common.base.Throwables.getRootCause;
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
import com.google.cloud.storage.BlobId;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.io.ByteStreams;
|
||||
@@ -42,6 +41,7 @@ import jakarta.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** Copy all registrar detail reports in a given bucket's subdirectory from GCS to Drive. */
|
||||
@Action(
|
||||
@@ -98,8 +98,7 @@ public final class CopyDetailReportsAction implements Runnable {
|
||||
response.setPayload(String.format("Failure, encountered %s", e.getMessage()));
|
||||
return;
|
||||
}
|
||||
ImmutableMultimap.Builder<String, Throwable> copyErrorsBuilder =
|
||||
new ImmutableMultimap.Builder<>();
|
||||
ImmutableMap.Builder<String, Throwable> copyErrorsBuilder = new ImmutableMap.Builder<>();
|
||||
for (String detailReportName : detailReportObjectNames) {
|
||||
// The standard report format is "invoice_details_yyyy-MM_registrarId_tld.csv
|
||||
// TODO(larryruili): Determine a safer way of enforcing this.
|
||||
@@ -146,18 +145,17 @@ public final class CopyDetailReportsAction implements Runnable {
|
||||
response.setStatus(SC_OK);
|
||||
response.setContentType(MediaType.PLAIN_TEXT_UTF_8);
|
||||
StringBuilder payload = new StringBuilder().append("Copied detail reports.\n");
|
||||
ImmutableMultimap<String, Throwable> copyErrors = copyErrorsBuilder.build();
|
||||
ImmutableMap<String, Throwable> copyErrors = copyErrorsBuilder.build();
|
||||
if (!copyErrors.isEmpty()) {
|
||||
payload.append("The following errors were encountered:\n");
|
||||
for (var registrarId : copyErrors.keySet()) {
|
||||
payload.append(
|
||||
String.format(
|
||||
"Registrar: %s\nError: %s\n",
|
||||
registrarId,
|
||||
copyErrors.get(registrarId).stream()
|
||||
.map(Throwable::getMessage)
|
||||
.collect(joining("\n\t"))));
|
||||
}
|
||||
payload.append(
|
||||
copyErrors.entrySet().stream()
|
||||
.map(
|
||||
entrySet ->
|
||||
String.format(
|
||||
"Registrar: %s\nError: %s\n",
|
||||
entrySet.getKey(), entrySet.getValue().getMessage()))
|
||||
.collect(Collectors.joining()));
|
||||
}
|
||||
response.setPayload(payload.toString());
|
||||
emailUtils.sendAlertEmail(payload.toString());
|
||||
|
||||
@@ -20,7 +20,6 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.keyring.api.KeyModule.Key;
|
||||
import google.registry.model.smd.SignedMarkRevocationList;
|
||||
import google.registry.model.smd.SignedMarkRevocationListDao;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.auth.Auth;
|
||||
@@ -58,7 +57,7 @@ public final class TmchSmdrlAction implements Runnable {
|
||||
} catch (GeneralSecurityException | IOException | PGPException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
smdrl = SignedMarkRevocationListDao.save(smdrl);
|
||||
smdrl.save();
|
||||
logger.atInfo().log(
|
||||
"Inserted %,d smd revocations into the database, created at %s.",
|
||||
smdrl.size(), smdrl.getCreationTime());
|
||||
|
||||
@@ -120,7 +120,7 @@ public class SecurityAction extends ConsoleApiAction {
|
||||
}
|
||||
}
|
||||
} catch (InsecureCertificateException e) {
|
||||
setFailedResponse(e.getMessage(), SC_BAD_REQUEST);
|
||||
setFailedResponse("Invalid certificate in parameter", SC_BAD_REQUEST);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -182,43 +182,6 @@ class ContactDeleteFlowTest extends ResourceFlowTestCase<ContactDeleteFlow, Cont
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_clientDeleteProhibited_superuser() throws Exception {
|
||||
persistResource(
|
||||
persistActiveContact(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.addStatusValue(StatusValue.CLIENT_DELETE_PROHIBITED)
|
||||
.build());
|
||||
runFlowAssertResponse(
|
||||
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("contact_delete_response.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_serverDeleteProhibited_superuser() throws Exception {
|
||||
persistResource(
|
||||
persistActiveContact(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.addStatusValue(StatusValue.SERVER_DELETE_PROHIBITED)
|
||||
.build());
|
||||
runFlowAssertResponse(
|
||||
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("contact_delete_response.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_pendingDelete_superuser() throws Exception {
|
||||
persistResource(
|
||||
persistActiveContact(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.addStatusValue(StatusValue.PENDING_DELETE)
|
||||
.build());
|
||||
assertAboutEppExceptions()
|
||||
.that(
|
||||
assertThrows(
|
||||
ResourceStatusProhibitsOperationException.class,
|
||||
() -> runFlow(CommitMode.LIVE, UserPrivileges.SUPERUSER)))
|
||||
.marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_unauthorizedClient() throws Exception {
|
||||
sessionMetadata.setRegistrarId("NewRegistrar");
|
||||
|
||||
@@ -179,7 +179,6 @@ import google.registry.model.reporting.DomainTransactionRecord;
|
||||
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
|
||||
import google.registry.model.smd.SignedMarkRevocationListDao;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.model.tld.Tld.TldState;
|
||||
import google.registry.model.tld.Tld.TldType;
|
||||
@@ -2728,9 +2727,8 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
|
||||
@Test
|
||||
void testFail_startDateSunriseRegistration_revokedSignedMark() throws Exception {
|
||||
SignedMarkRevocationListDao.save(
|
||||
SmdrlCsvParser.parse(
|
||||
TmchTestData.loadFile("smd/smdrl.csv").lines().collect(toImmutableList())));
|
||||
SmdrlCsvParser.parse(TmchTestData.loadFile("smd/smdrl.csv").lines().collect(toImmutableList()))
|
||||
.save();
|
||||
createTld("tld", START_DATE_SUNRISE);
|
||||
clock.setTo(SMD_VALID_TIME);
|
||||
String revokedSmd =
|
||||
@@ -2755,9 +2753,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
if (labels.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
SignedMarkRevocationListDao.save(
|
||||
SmdrlCsvParser.parse(
|
||||
TmchTestData.loadFile("idn/idn_smdrl.csv").lines().collect(toImmutableList())));
|
||||
SmdrlCsvParser.parse(
|
||||
TmchTestData.loadFile("idn/idn_smdrl.csv").lines().collect(toImmutableList()))
|
||||
.save();
|
||||
createTld("tld", START_DATE_SUNRISE);
|
||||
clock.setTo(SMD_VALID_TIME);
|
||||
String revokedSmd =
|
||||
|
||||
@@ -917,52 +917,6 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
|
||||
assertThat(thrown).hasMessageThat().contains("pendingDelete");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_clientDeleteProhibited_superuser() throws Exception {
|
||||
eppRequestSource = EppRequestSource.TOOL;
|
||||
setEppInput(
|
||||
"domain_delete_superuser_extension.xml",
|
||||
ImmutableMap.of("REDEMPTION_GRACE_PERIOD_DAYS", "15", "PENDING_DELETE_DAYS", "0"));
|
||||
setUpSuccessfulTest();
|
||||
|
||||
persistResource(
|
||||
domain.asBuilder().addStatusValue(StatusValue.CLIENT_DELETE_PROHIBITED).build());
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(
|
||||
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("domain_delete_response_pending.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_serverDeleteProhibited_superuser() throws Exception {
|
||||
eppRequestSource = EppRequestSource.TOOL;
|
||||
setEppInput(
|
||||
"domain_delete_superuser_extension.xml",
|
||||
ImmutableMap.of("REDEMPTION_GRACE_PERIOD_DAYS", "15", "PENDING_DELETE_DAYS", "0"));
|
||||
setUpSuccessfulTest();
|
||||
|
||||
persistResource(
|
||||
domain.asBuilder().addStatusValue(StatusValue.SERVER_DELETE_PROHIBITED).build());
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(
|
||||
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("domain_delete_response_pending.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_pendingDelete_superuser() throws Exception {
|
||||
eppRequestSource = EppRequestSource.TOOL;
|
||||
setEppInput(
|
||||
"domain_delete_superuser_extension.xml",
|
||||
ImmutableMap.of("REDEMPTION_GRACE_PERIOD_DAYS", "15", "PENDING_DELETE_DAYS", "0"));
|
||||
setUpSuccessfulTest();
|
||||
persistResource(domain.asBuilder().addStatusValue(StatusValue.PENDING_DELETE).build());
|
||||
|
||||
ResourceStatusProhibitsOperationException thrown =
|
||||
assertThrows(
|
||||
ResourceStatusProhibitsOperationException.class,
|
||||
() -> runFlow(CommitMode.LIVE, UserPrivileges.SUPERUSER));
|
||||
assertThat(thrown).hasMessageThat().contains("pendingDelete");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_metadata() throws Exception {
|
||||
eppRequestSource = EppRequestSource.TOOL;
|
||||
|
||||
@@ -346,18 +346,18 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_minimumDatasetPhase2_whenAddingNewContacts() throws Exception {
|
||||
void testFailure_minimumDatasetPhase2_nonRegistrantContactsStillExist() throws Exception {
|
||||
persistResource(
|
||||
new FeatureFlag.Builder()
|
||||
.setFeatureName(MINIMUM_DATASET_CONTACTS_PROHIBITED)
|
||||
.setStatusMap(
|
||||
ImmutableSortedMap.of(START_OF_TIME, INACTIVE, clock.nowUtc().minusDays(5), ACTIVE))
|
||||
.build());
|
||||
// This EPP adds a new technical contact mak21 that wasn't already present.
|
||||
setEppInput("domain_update_empty_registrant.xml");
|
||||
persistReferencedEntities();
|
||||
persistDomain();
|
||||
// Fails because the update adds some new contacts, although the registrant has been removed.
|
||||
// Fails because after the update the domain would still have some contacts on it even though
|
||||
// the registrant has been removed.
|
||||
ContactsProhibitedException thrown =
|
||||
assertThrows(ContactsProhibitedException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
@@ -1574,13 +1574,14 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_minimumDatasetPhase2_addingNewRegistrantFails() throws Exception {
|
||||
void testFailure_minimumDatasetPhase2_registrantStillExists() throws Exception {
|
||||
persistResource(
|
||||
new FeatureFlag.Builder()
|
||||
.setFeatureName(MINIMUM_DATASET_CONTACTS_PROHIBITED)
|
||||
.setStatusMap(
|
||||
ImmutableSortedMap.of(START_OF_TIME, INACTIVE, clock.nowUtc().minusDays(5), ACTIVE))
|
||||
.build());
|
||||
setEppInput("domain_update_remove_admin.xml");
|
||||
persistReferencedEntities();
|
||||
persistResource(
|
||||
DatabaseHelper.newDomain(getUniqueIdFromCommand())
|
||||
@@ -1589,10 +1590,7 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
ImmutableSet.of(
|
||||
DesignatedContact.create(Type.ADMIN, sh8013Contact.createVKey()),
|
||||
DesignatedContact.create(Type.TECH, sh8013Contact.createVKey())))
|
||||
.setRegistrant(Optional.empty())
|
||||
.build());
|
||||
// This EPP sets the registrant to sh8013, whereas in our test setup it is absent.
|
||||
setEppInput("domain_update_registrant.xml");
|
||||
RegistrantProhibitedException thrown =
|
||||
assertThrows(RegistrantProhibitedException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
@@ -1659,32 +1657,6 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
assertThat(updatedDomain.getContacts()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_minimumDatasetPhase2_removeOneContact() throws Exception {
|
||||
persistResource(
|
||||
new FeatureFlag.Builder()
|
||||
.setFeatureName(MINIMUM_DATASET_CONTACTS_PROHIBITED)
|
||||
.setStatusMap(
|
||||
ImmutableSortedMap.of(START_OF_TIME, INACTIVE, clock.nowUtc().minusDays(5), ACTIVE))
|
||||
.build());
|
||||
setEppInput("domain_update_remove_admin.xml");
|
||||
persistReferencedEntities();
|
||||
persistResource(
|
||||
DatabaseHelper.newDomain(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.setContacts(
|
||||
ImmutableSet.of(
|
||||
DesignatedContact.create(Type.ADMIN, sh8013Contact.createVKey()),
|
||||
DesignatedContact.create(Type.TECH, sh8013Contact.createVKey())))
|
||||
.build());
|
||||
assertThat(reloadResourceByForeignKey().getRegistrant()).isPresent();
|
||||
assertThat(reloadResourceByForeignKey().getContacts()).hasSize(2);
|
||||
runFlowAssertResponse(loadFile("generic_success_response.xml"));
|
||||
Domain updatedDomain = reloadResourceByForeignKey();
|
||||
assertThat(updatedDomain.getRegistrant()).isPresent();
|
||||
assertThat(updatedDomain.getContacts()).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_addPendingDeleteContact() throws Exception {
|
||||
persistReferencedEntities();
|
||||
|
||||
@@ -153,43 +153,6 @@ class HostDeleteFlowTest extends ResourceFlowTestCase<HostDeleteFlow, Host> {
|
||||
assertSqlDeleteSuccess();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_clientDeleteProhibited_superuser() throws Exception {
|
||||
persistResource(
|
||||
persistActiveHost("ns1.example.tld")
|
||||
.asBuilder()
|
||||
.addStatusValue(StatusValue.CLIENT_DELETE_PROHIBITED)
|
||||
.build());
|
||||
runFlowAssertResponse(
|
||||
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("host_delete_response.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_serverDeleteProhibited_superuser() throws Exception {
|
||||
persistResource(
|
||||
persistActiveHost("ns1.example.tld")
|
||||
.asBuilder()
|
||||
.addStatusValue(StatusValue.SERVER_DELETE_PROHIBITED)
|
||||
.build());
|
||||
runFlowAssertResponse(
|
||||
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("host_delete_response.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_pendingDelete_superuser() throws Exception {
|
||||
persistResource(
|
||||
persistActiveHost("ns1.example.tld")
|
||||
.asBuilder()
|
||||
.addStatusValue(StatusValue.PENDING_DELETE)
|
||||
.build());
|
||||
assertAboutEppExceptions()
|
||||
.that(
|
||||
assertThrows(
|
||||
ResourceStatusProhibitsOperationException.class,
|
||||
() -> runFlow(CommitMode.LIVE, UserPrivileges.SUPERUSER)))
|
||||
.marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_authorizedClientReadFromSuperordinate() throws Exception {
|
||||
sessionMetadata.setRegistrarId("TheRegistrar");
|
||||
|
||||
@@ -16,12 +16,9 @@ package google.registry.model.smd;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.model.EntityTestCase;
|
||||
import jakarta.persistence.OptimisticLockException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class SignedMarkRevocationListDaoTest extends EntityTestCase {
|
||||
@@ -35,29 +32,11 @@ public class SignedMarkRevocationListDaoTest extends EntityTestCase {
|
||||
SignedMarkRevocationList list =
|
||||
SignedMarkRevocationList.create(
|
||||
fakeClock.nowUtc(), ImmutableMap.of("mark", fakeClock.nowUtc().minusHours(1)));
|
||||
list = SignedMarkRevocationListDao.save(list);
|
||||
SignedMarkRevocationListDao.save(list);
|
||||
SignedMarkRevocationList fromDb = SignedMarkRevocationListDao.load();
|
||||
assertAboutImmutableObjects().that(fromDb).isEqualExceptFields(list);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSave_retrySuccess() {
|
||||
SignedMarkRevocationList list =
|
||||
SignedMarkRevocationList.create(
|
||||
fakeClock.nowUtc(), ImmutableMap.of("mark", fakeClock.nowUtc().minusHours(1)));
|
||||
AtomicBoolean isFirstAttempt = new AtomicBoolean(true);
|
||||
tm().transact(
|
||||
() -> {
|
||||
SignedMarkRevocationListDao.save(list);
|
||||
if (isFirstAttempt.get()) {
|
||||
isFirstAttempt.set(false);
|
||||
throw new OptimisticLockException();
|
||||
}
|
||||
});
|
||||
SignedMarkRevocationList fromDb = SignedMarkRevocationListDao.load();
|
||||
assertAboutImmutableObjects().that(fromDb).isEqualExceptFields(list, "revisionId");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSaveAndLoad_emptyList() {
|
||||
SignedMarkRevocationList list =
|
||||
|
||||
@@ -48,8 +48,7 @@ public class SignedMarkRevocationListTest {
|
||||
for (int i = 0; i < rows; i++) {
|
||||
revokes.put(Integer.toString(i), clock.nowUtc());
|
||||
}
|
||||
SignedMarkRevocationListDao.save(
|
||||
SignedMarkRevocationList.create(clock.nowUtc(), revokes.build()));
|
||||
SignedMarkRevocationList.create(clock.nowUtc(), revokes.build()).save();
|
||||
SignedMarkRevocationList res = SignedMarkRevocationList.get();
|
||||
assertThat(res.size()).isEqualTo(rows);
|
||||
return res;
|
||||
|
||||
@@ -31,12 +31,10 @@ import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationWithCoverageExtension;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.TestCacheExtension;
|
||||
import jakarta.persistence.OptimisticLockException;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.stream.IntStream;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.money.Money;
|
||||
@@ -95,27 +93,6 @@ public class PremiumListDaoTest {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveNew_retry_success() {
|
||||
AtomicBoolean isFirstAttempt = new AtomicBoolean(true);
|
||||
tm().transact(
|
||||
() -> {
|
||||
PremiumListDao.save(testList);
|
||||
if (isFirstAttempt.get()) {
|
||||
isFirstAttempt.set(false);
|
||||
throw new OptimisticLockException();
|
||||
}
|
||||
});
|
||||
tm().transact(
|
||||
() -> {
|
||||
Optional<PremiumList> persistedListOpt = PremiumListDao.getLatestRevision("testname");
|
||||
assertThat(persistedListOpt).isPresent();
|
||||
PremiumList persistedList = persistedListOpt.get();
|
||||
assertThat(persistedList.getLabelsToPrices()).containsExactlyEntriesIn(TEST_PRICES);
|
||||
assertThat(persistedList.getCreationTimestamp()).isEqualTo(fakeClock.nowUtc());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void update_worksSuccessfully() {
|
||||
PremiumListDao.save(testList);
|
||||
|
||||
@@ -22,8 +22,6 @@ import google.registry.model.tld.label.ReservedList.ReservedListEntry;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationWithCoverageExtension;
|
||||
import google.registry.testing.FakeClock;
|
||||
import jakarta.persistence.OptimisticLockException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
@@ -73,34 +71,11 @@ public class ReservedListDaoTest {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void save_withRetry_worksSuccessfully() {
|
||||
AtomicBoolean isFirstAttempt = new AtomicBoolean(true);
|
||||
tm().transact(
|
||||
() -> {
|
||||
ReservedListDao.save(testReservedList);
|
||||
if (isFirstAttempt.get()) {
|
||||
isFirstAttempt.set(false);
|
||||
throw new OptimisticLockException();
|
||||
}
|
||||
});
|
||||
tm().transact(
|
||||
() -> {
|
||||
ReservedList persistedList =
|
||||
tm().query("FROM ReservedList WHERE name = :name", ReservedList.class)
|
||||
.setParameter("name", "testlist")
|
||||
.getSingleResult();
|
||||
assertThat(persistedList.getReservedListEntries())
|
||||
.containsExactlyEntriesIn(testReservations);
|
||||
assertThat(persistedList.getCreationTimestamp()).isEqualTo(fakeClock.nowUtc());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void delete_worksSuccessfully() {
|
||||
var persisted = ReservedListDao.save(testReservedList);
|
||||
ReservedListDao.save(testReservedList);
|
||||
assertThat(ReservedListDao.checkExists("testlist")).isTrue();
|
||||
ReservedListDao.delete(persisted);
|
||||
ReservedListDao.delete(testReservedList);
|
||||
assertThat(ReservedListDao.checkExists("testlist")).isFalse();
|
||||
}
|
||||
|
||||
|
||||
@@ -16,15 +16,15 @@ package google.registry.model.tmch;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationWithCoverageExtension;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.TestCacheExtension;
|
||||
import jakarta.persistence.OptimisticLockException;
|
||||
import jakarta.persistence.PersistenceException;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
@@ -49,36 +49,27 @@ public class ClaimsListDaoTest {
|
||||
void save_insertsClaimsListSuccessfully() {
|
||||
ClaimsList claimsList =
|
||||
ClaimsList.create(fakeClock.nowUtc(), ImmutableMap.of("label1", "key1", "label2", "key2"));
|
||||
claimsList = ClaimsListDao.save(claimsList);
|
||||
ClaimsListDao.save(claimsList);
|
||||
ClaimsList insertedClaimsList = ClaimsListDao.get();
|
||||
assertClaimsListEquals(claimsList, insertedClaimsList);
|
||||
assertThat(insertedClaimsList.getCreationTimestamp()).isEqualTo(fakeClock.nowUtc());
|
||||
}
|
||||
|
||||
@Test
|
||||
void save_insertsClaimsListSuccessfully_withRetries() {
|
||||
void save_fail_duplicateId() {
|
||||
ClaimsList claimsList =
|
||||
ClaimsList.create(fakeClock.nowUtc(), ImmutableMap.of("label1", "key1", "label2", "key2"));
|
||||
AtomicBoolean isFirstAttempt = new AtomicBoolean(true);
|
||||
tm().transact(
|
||||
() -> {
|
||||
ClaimsListDao.save(claimsList);
|
||||
if (isFirstAttempt.get()) {
|
||||
isFirstAttempt.set(false);
|
||||
throw new OptimisticLockException();
|
||||
}
|
||||
});
|
||||
ClaimsListDao.save(claimsList);
|
||||
ClaimsList insertedClaimsList = ClaimsListDao.get();
|
||||
assertThat(insertedClaimsList.getTmdbGenerationTime())
|
||||
.isEqualTo(claimsList.getTmdbGenerationTime());
|
||||
assertThat(insertedClaimsList.getLabelsToKeys()).isEqualTo(claimsList.getLabelsToKeys());
|
||||
assertThat(insertedClaimsList.getCreationTimestamp()).isEqualTo(fakeClock.nowUtc());
|
||||
assertClaimsListEquals(claimsList, insertedClaimsList);
|
||||
// Save ClaimsList with existing revisionId should fail because revisionId is the primary key.
|
||||
assertThrows(PersistenceException.class, () -> ClaimsListDao.save(insertedClaimsList));
|
||||
}
|
||||
|
||||
@Test
|
||||
void save_claimsListWithNoEntries() {
|
||||
ClaimsList claimsList = ClaimsList.create(fakeClock.nowUtc(), ImmutableMap.of());
|
||||
claimsList = ClaimsListDao.save(claimsList);
|
||||
ClaimsListDao.save(claimsList);
|
||||
ClaimsList insertedClaimsList = ClaimsListDao.get();
|
||||
assertClaimsListEquals(claimsList, insertedClaimsList);
|
||||
assertThat(insertedClaimsList.getLabelsToKeys()).isEmpty();
|
||||
@@ -95,8 +86,8 @@ public class ClaimsListDaoTest {
|
||||
ClaimsList.create(fakeClock.nowUtc(), ImmutableMap.of("label1", "key1", "label2", "key2"));
|
||||
ClaimsList newClaimsList =
|
||||
ClaimsList.create(fakeClock.nowUtc(), ImmutableMap.of("label3", "key3", "label4", "key4"));
|
||||
oldClaimsList = ClaimsListDao.save(oldClaimsList);
|
||||
newClaimsList = ClaimsListDao.save(newClaimsList);
|
||||
ClaimsListDao.save(oldClaimsList);
|
||||
ClaimsListDao.save(newClaimsList);
|
||||
assertClaimsListEquals(newClaimsList, ClaimsListDao.get());
|
||||
}
|
||||
|
||||
@@ -105,11 +96,11 @@ public class ClaimsListDaoTest {
|
||||
assertThat(ClaimsListDao.CACHE.getIfPresent(ClaimsListDao.class)).isNull();
|
||||
ClaimsList oldList =
|
||||
ClaimsList.create(fakeClock.nowUtc(), ImmutableMap.of("label1", "key1", "label2", "key2"));
|
||||
oldList = ClaimsListDao.save(oldList);
|
||||
ClaimsListDao.save(oldList);
|
||||
assertThat(ClaimsListDao.CACHE.getIfPresent(ClaimsListDao.class)).isEqualTo(oldList);
|
||||
ClaimsList newList =
|
||||
ClaimsList.create(fakeClock.nowUtc(), ImmutableMap.of("label3", "key3", "label4", "key4"));
|
||||
newList = ClaimsListDao.save(newList);
|
||||
ClaimsListDao.save(newList);
|
||||
assertThat(ClaimsListDao.CACHE.getIfPresent(ClaimsListDao.class)).isEqualTo(newList);
|
||||
}
|
||||
|
||||
|
||||
@@ -178,56 +178,21 @@ class CopyDetailReportsActionTest {
|
||||
verify(emailUtils)
|
||||
.sendAlertEmail(
|
||||
"""
|
||||
Copied detail reports.
|
||||
The following errors were encountered:
|
||||
Registrar: TheRegistrar
|
||||
Error: java.io.IOException: expected
|
||||
""");
|
||||
Copied detail reports.
|
||||
The following errors were encountered:
|
||||
Registrar: TheRegistrar
|
||||
Error: java.io.IOException: expected
|
||||
""");
|
||||
assertThat(response.getStatus()).isEqualTo(SC_OK);
|
||||
assertThat(response.getContentType()).isEqualTo(MediaType.PLAIN_TEXT_UTF_8);
|
||||
assertThat(response.getPayload())
|
||||
.isEqualTo(
|
||||
"""
|
||||
Copied detail reports.
|
||||
The following errors were encountered:
|
||||
Registrar: TheRegistrar
|
||||
Error: java.io.IOException: expected
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFail_tooManyFailures_one_registrar_sendsAlertEmail_continues() throws IOException {
|
||||
gcsUtils.createFromBytes(
|
||||
BlobId.of("test-bucket", "results/invoice_details_2017-10_TheRegistrar_hello.csv"),
|
||||
"hola,mundo\n3,4".getBytes(UTF_8));
|
||||
|
||||
gcsUtils.createFromBytes(
|
||||
BlobId.of("test-bucket", "results/invoice_details_2017-10_TheRegistrar_test.csv"),
|
||||
"hello,world\n1,2".getBytes(UTF_8));
|
||||
when(driveConnection.createOrUpdateFile(any(), any(), any(), any()))
|
||||
.thenThrow(new IOException("expected"));
|
||||
|
||||
action.run();
|
||||
verify(emailUtils)
|
||||
.sendAlertEmail(
|
||||
"""
|
||||
Copied detail reports.
|
||||
The following errors were encountered:
|
||||
Registrar: TheRegistrar
|
||||
Error: java.io.IOException: expected
|
||||
\tjava.io.IOException: expected
|
||||
""");
|
||||
assertThat(response.getStatus()).isEqualTo(SC_OK);
|
||||
assertThat(response.getContentType()).isEqualTo(MediaType.PLAIN_TEXT_UTF_8);
|
||||
assertThat(response.getPayload())
|
||||
.isEqualTo(
|
||||
"""
|
||||
Copied detail reports.
|
||||
The following errors were encountered:
|
||||
Registrar: TheRegistrar
|
||||
Error: java.io.IOException: expected
|
||||
\tjava.io.IOException: expected
|
||||
""");
|
||||
Copied detail reports.
|
||||
The following errors were encountered:
|
||||
Registrar: TheRegistrar
|
||||
Error: java.io.IOException: expected
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -20,7 +20,6 @@ import static google.registry.testing.DatabaseHelper.loadRegistrar;
|
||||
import static google.registry.testing.DatabaseHelper.loadSingleton;
|
||||
import static google.registry.testing.SqlHelper.saveRegistrar;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -55,52 +54,30 @@ class SecurityActionTest extends ConsoleActionBaseTestCase {
|
||||
SAMPLE_CERT2);
|
||||
private Registrar testRegistrar;
|
||||
|
||||
private static final String VALIDITY_TOO_LONG_CERT_PEM =
|
||||
"-----BEGIN CERTIFICATE-----\n"
|
||||
+ "MIIDejCCAv+gAwIBAgIQHNcSEt4VENkSgtozEEoQLzAKBggqhkjOPQQDAzB8MQsw\n"
|
||||
+ "CQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xGDAW\n"
|
||||
+ "BgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBSb290IENl\n"
|
||||
+ "cnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzAeFw0xOTAzMDcxOTQyNDJaFw0zNDAz\n"
|
||||
+ "MDMxOTQyNDJaMG8xCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UE\n"
|
||||
+ "BwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxKzApBgNVBAMMIlNTTC5jb20g\n"
|
||||
+ "U1NMIEludGVybWVkaWF0ZSBDQSBFQ0MgUjIwdjAQBgcqhkjOPQIBBgUrgQQAIgNi\n"
|
||||
+ "AASEOWn30uEYKDLFu4sCjFQ1VupFaeMtQjqVWyWSA7+KFljnsVaFQ2hgs4cQk1f/\n"
|
||||
+ "RQ2INSwdVCYU0i5qsbom20rigUhDh9dM/r6bEZ75eFE899kSCI14xqThYVLPdLEl\n"
|
||||
+ "+dyjggFRMIIBTTASBgNVHRMBAf8ECDAGAQH/AgEAMB8GA1UdIwQYMBaAFILRhXMw\n"
|
||||
+ "5zUE044CkvvlpNHEIejNMHgGCCsGAQUFBwEBBGwwajBGBggrBgEFBQcwAoY6aHR0\n"
|
||||
+ "cDovL3d3dy5zc2wuY29tL3JlcG9zaXRvcnkvU1NMY29tLVJvb3RDQS1FQ0MtMzg0\n"
|
||||
+ "LVIxLmNydDAgBggrBgEFBQcwAYYUaHR0cDovL29jc3BzLnNzbC5jb20wEQYDVR0g\n"
|
||||
+ "BAowCDAGBgRVHSAAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATA7BgNV\n"
|
||||
+ "HR8ENDAyMDCgLqAshipodHRwOi8vY3Jscy5zc2wuY29tL3NzbC5jb20tZWNjLVJv\n"
|
||||
+ "b3RDQS5jcmwwHQYDVR0OBBYEFA10Zgpen+Is7NXCXSUEf3Uyuv99MA4GA1UdDwEB\n"
|
||||
+ "/wQEAwIBhjAKBggqhkjOPQQDAwNpADBmAjEAxYt6Ylk/N8Fch/3fgKYKwI5A011Q\n"
|
||||
+ "MKW0h3F9JW/NX/F7oYtWrxljheH8n2BrkDybAjEAlCxkLE0vQTYcFzrR24oogyw6\n"
|
||||
+ "VkgTm92+jiqJTO5SSA9QUa092S5cTKiHkH2cOM6m\n"
|
||||
+ "-----END CERTIFICATE-----";
|
||||
|
||||
private AuthenticatedRegistrarAccessor registrarAccessor =
|
||||
AuthenticatedRegistrarAccessor.createForTesting(
|
||||
ImmutableSetMultimap.of("registrarId", AuthenticatedRegistrarAccessor.Role.ADMIN));
|
||||
|
||||
private CertificateChecker certificateChecker =
|
||||
new CertificateChecker(
|
||||
ImmutableSortedMap.of(START_OF_TIME, 20825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
|
||||
30,
|
||||
15,
|
||||
2048,
|
||||
ImmutableSet.of("secp256r1", "secp384r1"),
|
||||
clock);
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
testRegistrar = saveRegistrar("registrarId");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_postRegistrarInfo() throws IOException {
|
||||
CertificateChecker lenientChecker =
|
||||
new CertificateChecker(
|
||||
ImmutableSortedMap.of(
|
||||
START_OF_TIME, 20825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
|
||||
30,
|
||||
15,
|
||||
2048,
|
||||
ImmutableSet.of("secp256r1", "secp384r1"),
|
||||
clock);
|
||||
|
||||
clock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
|
||||
SecurityAction action = createAction(testRegistrar.getRegistrarId(), lenientChecker);
|
||||
SecurityAction action =
|
||||
createAction(
|
||||
testRegistrar.getRegistrarId());
|
||||
action.run();
|
||||
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK);
|
||||
Registrar r = loadRegistrar(testRegistrar.getRegistrarId());
|
||||
@@ -113,39 +90,9 @@ class SecurityActionTest extends ConsoleActionBaseTestCase {
|
||||
assertThat(history.getDescription()).hasValue("registrarId|IP_CHANGE,PRIMARY_SSL_CERT_CHANGE");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_validityPeriodTooLong_returnsSpecificError() throws IOException {
|
||||
CertificateChecker strictChecker =
|
||||
new CertificateChecker(
|
||||
ImmutableSortedMap.of(START_OF_TIME, 398),
|
||||
30,
|
||||
15,
|
||||
2048,
|
||||
ImmutableSet.of("secp256r1", "secp384r1"),
|
||||
clock);
|
||||
|
||||
clock.setTo(DateTime.parse("2025-01-01T00:00:00Z"));
|
||||
String escapedCert = VALIDITY_TOO_LONG_CERT_PEM.replace("\n", "\\n");
|
||||
String jsonWithBadCert =
|
||||
String.format(
|
||||
"{\"registrarId\": \"registrarId\", \"clientCertificate\": \"%s\"}", escapedCert);
|
||||
|
||||
SecurityAction action =
|
||||
createAction(testRegistrar.getRegistrarId(), jsonWithBadCert, strictChecker);
|
||||
action.run();
|
||||
|
||||
String expectedError =
|
||||
"Certificate validity period is too long; it must be less than or equal to 398 days.";
|
||||
FakeResponse response = (FakeResponse) consoleApiParams.response();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_BAD_REQUEST);
|
||||
assertThat(response.getPayload()).isEqualTo(expectedError);
|
||||
}
|
||||
|
||||
private SecurityAction createAction(
|
||||
String registrarId, String jsonBody, CertificateChecker certificateChecker)
|
||||
throws IOException {
|
||||
private SecurityAction createAction(String registrarId) throws IOException {
|
||||
when(consoleApiParams.request().getMethod()).thenReturn(Action.Method.POST.toString());
|
||||
doReturn(new BufferedReader(new StringReader(jsonBody)))
|
||||
doReturn(new BufferedReader(new StringReader(jsonRegistrar1)))
|
||||
.when(consoleApiParams.request())
|
||||
.getReader();
|
||||
Optional<Registrar> maybeRegistrar =
|
||||
@@ -154,9 +101,4 @@ class SecurityActionTest extends ConsoleActionBaseTestCase {
|
||||
return new SecurityAction(
|
||||
consoleApiParams, certificateChecker, registrarAccessor, registrarId, maybeRegistrar);
|
||||
}
|
||||
|
||||
private SecurityAction createAction(String registrarId, CertificateChecker certificateChecker)
|
||||
throws IOException {
|
||||
return createAction(registrarId, jsonRegistrar1, certificateChecker);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ buildscript {
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "org.flywaydb.flyway" version "11.0.1"
|
||||
id "org.flywaydb.flyway" version "9.22.3"
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# This file defines properties used by the gradle build. It must be kept in
|
||||
f# This file defines properties used by the gradle build. It must be kept in
|
||||
# sync with config/nom_build.py.
|
||||
#
|
||||
# To regenerate, run ./nom_build --generate-gradle-properties
|
||||
|
||||
@@ -25,7 +25,7 @@ spec:
|
||||
value: /_dr/epptool
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /_dr/loadtest
|
||||
value: /loadtest
|
||||
backendRefs:
|
||||
- group: net.gke.io
|
||||
kind: ServiceImport
|
||||
@@ -58,7 +58,7 @@ spec:
|
||||
value: "true"
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /_dr/loadtest
|
||||
value: /loadtest
|
||||
headers:
|
||||
- name: "canary"
|
||||
value: "true"
|
||||
|
||||
@@ -25,11 +25,8 @@ spec:
|
||||
name: http
|
||||
resources:
|
||||
requests:
|
||||
cpu: "500m"
|
||||
memory: "1Gi"
|
||||
limits:
|
||||
cpu: "1000m"
|
||||
memory: "1Gi"
|
||||
cpu: "100m"
|
||||
memory: "512Mi"
|
||||
args: [ENVIRONMENT]
|
||||
env:
|
||||
- name: POD_ID
|
||||
@@ -64,7 +61,7 @@ spec:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
averageUtilization: 100
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
|
||||
@@ -41,7 +41,7 @@ spec:
|
||||
# class from performance, which has implicit pod-slots 1
|
||||
cloud.google.com/pod-slots: 0
|
||||
cpu: "500m"
|
||||
memory: "1Gi"
|
||||
memory: "2Gi"
|
||||
args: [ENVIRONMENT]
|
||||
env:
|
||||
- name: POD_ID
|
||||
@@ -76,7 +76,7 @@ spec:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
averageUtilization: 100
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
|
||||
@@ -25,11 +25,8 @@ spec:
|
||||
name: http
|
||||
resources:
|
||||
requests:
|
||||
cpu: "1000m"
|
||||
cpu: "100m"
|
||||
memory: "1Gi"
|
||||
limits:
|
||||
cpu: "1000m"
|
||||
memory: "2Gi"
|
||||
args: [ENVIRONMENT]
|
||||
env:
|
||||
- name: POD_ID
|
||||
@@ -53,10 +50,7 @@ spec:
|
||||
name: epp
|
||||
resources:
|
||||
requests:
|
||||
cpu: "1000m"
|
||||
memory: "512Mi"
|
||||
limits:
|
||||
cpu: "1000m"
|
||||
cpu: "100m"
|
||||
memory: "512Mi"
|
||||
args: [--env, PROXY_ENV, --log, --local]
|
||||
env:
|
||||
@@ -91,12 +85,12 @@ spec:
|
||||
minReplicas: 5
|
||||
maxReplicas: 20
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 100
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
|
||||
@@ -34,13 +34,13 @@ spec:
|
||||
# explicit pod-slots 0 is required in order to downgrade node
|
||||
# class from performance, which has implicit pod-slots 1
|
||||
cloud.google.com/pod-slots: 0
|
||||
cpu: "500m"
|
||||
cpu: "100m"
|
||||
memory: "1Gi"
|
||||
limits:
|
||||
# explicit pod-slots 0 is required in order to downgrade node
|
||||
# class from performance, which has implicit pod-slots 1
|
||||
cloud.google.com/pod-slots: 0
|
||||
cpu: "1000m"
|
||||
cpu: "500m"
|
||||
memory: "2Gi"
|
||||
args: [ENVIRONMENT]
|
||||
env:
|
||||
@@ -76,7 +76,7 @@ spec:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
averageUtilization: 100
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
|
||||
@@ -33,7 +33,6 @@ do
|
||||
--project "${project}" --zone "${parts[1]}"
|
||||
sed s/GCP_PROJECT/${project}/g "./kubernetes/proxy-deployment-${environment}.yaml" | \
|
||||
kubectl apply -f -
|
||||
kubectl apply -f "./kubernetes/proxy-limit-range.yaml" --force
|
||||
kubectl apply -f "./kubernetes/proxy-service.yaml" --force
|
||||
# Alpha does not have canary
|
||||
if [[ ${environment} != "alpha" ]]; then
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: LimitRange
|
||||
metadata:
|
||||
name: resource-limits
|
||||
namespace: default
|
||||
spec:
|
||||
limits:
|
||||
- type: Container
|
||||
default:
|
||||
cpu: "300m"
|
||||
memory: "512Mi"
|
||||
defaultRequest:
|
||||
cpu: "100m"
|
||||
memory: "350Mi"
|
||||
@@ -33,10 +33,3 @@ spec:
|
||||
name: proxy-deployment-canary
|
||||
maxReplicas: 10
|
||||
minReplicas: 1
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 100
|
||||
|
||||
@@ -33,11 +33,3 @@ spec:
|
||||
name: proxy-deployment
|
||||
maxReplicas: 50
|
||||
minReplicas: 10
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 100
|
||||
|
||||
|
||||
@@ -19,11 +19,8 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static google.registry.networking.handler.SslServerInitializer.CLIENT_CERTIFICATE_PROMISE_KEY;
|
||||
import static google.registry.proxy.handler.ProxyProtocolHandler.REMOTE_ADDRESS_KEY;
|
||||
import static google.registry.util.X509Utils.getCertificateHash;
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.io.BaseEncoding;
|
||||
import google.registry.proxy.metric.FrontendMetrics;
|
||||
import google.registry.util.ProxyHttpHeaders;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
@@ -39,11 +36,7 @@ import io.netty.handler.ssl.SslHandshakeCompletionEvent;
|
||||
import io.netty.util.AttributeKey;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** Handler that processes EPP protocol logic. */
|
||||
public class EppServiceHandler extends HttpsRelayServiceHandler {
|
||||
@@ -64,8 +57,6 @@ public class EppServiceHandler extends HttpsRelayServiceHandler {
|
||||
private String sslClientCertificateHash;
|
||||
private String clientAddress;
|
||||
|
||||
private Optional<String> maybeRegistrarId = Optional.empty();
|
||||
|
||||
public EppServiceHandler(
|
||||
String relayHost,
|
||||
String relayPath,
|
||||
@@ -137,9 +128,6 @@ public class EppServiceHandler extends HttpsRelayServiceHandler {
|
||||
.set(ProxyHttpHeaders.FALLBACK_IP_ADDRESS, clientAddress)
|
||||
.set(HttpHeaderNames.CONTENT_TYPE, EPP_CONTENT_TYPE)
|
||||
.set(HttpHeaderNames.ACCEPT, EPP_CONTENT_TYPE);
|
||||
|
||||
maybeSetRegistrarIdHeader(request);
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
@@ -154,54 +142,4 @@ public class EppServiceHandler extends HttpsRelayServiceHandler {
|
||||
}
|
||||
super.write(ctx, msg, promise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets and caches the Registrar-ID header on the request if the ID can be found.
|
||||
*
|
||||
* <p>This method first checks if the registrar ID has already been determined. If not, it
|
||||
* inspects the cookies for a "SESSION_INFO" cookie, from which it attempts to extract the
|
||||
* registrar ID.
|
||||
*
|
||||
* @param request The {@link FullHttpRequest} on which to potentially set the registrar ID header.
|
||||
* @see #extractRegistrarIdFromSessionInfo(String)
|
||||
*/
|
||||
private void maybeSetRegistrarIdHeader(FullHttpRequest request) {
|
||||
if (maybeRegistrarId.isEmpty()) {
|
||||
maybeRegistrarId =
|
||||
cookieStore.entrySet().stream()
|
||||
.map(e -> e.getValue())
|
||||
.filter(cookie -> "SESSION_INFO".equals(cookie.name()))
|
||||
.findFirst()
|
||||
.flatMap(cookie -> extractRegistrarIdFromSessionInfo(cookie.value()));
|
||||
}
|
||||
|
||||
if (maybeRegistrarId.isPresent() && !Strings.isNullOrEmpty(maybeRegistrarId.get())) {
|
||||
request.headers().set(ProxyHttpHeaders.REGISTRAR_ID, maybeRegistrarId.get());
|
||||
}
|
||||
}
|
||||
|
||||
/** Extracts the registrar ID from a Base64-encoded session info string. */
|
||||
private Optional<String> extractRegistrarIdFromSessionInfo(@Nullable String sessionInfo) {
|
||||
if (sessionInfo == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
try {
|
||||
String decodedString = new String(BaseEncoding.base64Url().decode(sessionInfo), US_ASCII);
|
||||
Pattern pattern = Pattern.compile("clientId=([^,\\s]+)?");
|
||||
Matcher matcher = pattern.matcher(decodedString);
|
||||
|
||||
if (matcher.find()) {
|
||||
String maybeRegistrarIdMatch = matcher.group(1);
|
||||
if (!maybeRegistrarIdMatch.equals("null")) {
|
||||
return Optional.of(maybeRegistrarIdMatch);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Throwable e) {
|
||||
logger.atSevere().withCause(e).log("Failed to decode session info from Base64");
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public abstract class HttpsRelayServiceHandler extends ByteToMessageCodec<FullHt
|
||||
protected static final ImmutableSet<Class<? extends Exception>> NON_FATAL_OUTBOUND_EXCEPTIONS =
|
||||
ImmutableSet.of(NonOkHttpResponseException.class);
|
||||
|
||||
protected final Map<String, Cookie> cookieStore = new LinkedHashMap<>();
|
||||
private final Map<String, Cookie> cookieStore = new LinkedHashMap<>();
|
||||
private final String relayHost;
|
||||
private final String relayPath;
|
||||
private final boolean canary;
|
||||
|
||||
@@ -20,7 +20,6 @@ import static google.registry.proxy.TestUtils.assertHttpRequestEquivalent;
|
||||
import static google.registry.proxy.TestUtils.makeEppHttpResponse;
|
||||
import static google.registry.proxy.handler.ProxyProtocolHandler.REMOTE_ADDRESS_KEY;
|
||||
import static google.registry.util.X509Utils.getCertificateHash;
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -28,7 +27,6 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.io.BaseEncoding;
|
||||
import google.registry.proxy.TestUtils;
|
||||
import google.registry.proxy.handler.HttpsRelayServiceHandler.NonOkHttpResponseException;
|
||||
import google.registry.proxy.metric.FrontendMetrics;
|
||||
@@ -359,82 +357,4 @@ class EppServiceHandlerTest {
|
||||
assertThat((Object) channel.readOutbound()).isNull();
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_registrarIdHeader_isSetFromSessionInfoCookie() throws Exception {
|
||||
setHandshakeSuccess();
|
||||
channel.readInbound(); // Read and discard the initial hello request.
|
||||
|
||||
// Simulate a server response that sets the SESSION_INFO cookie.
|
||||
String registrarId = "TheRegistrar";
|
||||
String sessionInfoValue =
|
||||
BaseEncoding.base64Url()
|
||||
.encode(("alpha,clientId=" + registrarId + ",beta").getBytes(US_ASCII));
|
||||
Cookie sessionCookie = new DefaultCookie("SESSION_INFO", sessionInfoValue);
|
||||
channel.writeOutbound(
|
||||
makeEppHttpResponse("<epp>greeting</epp>", HttpResponseStatus.OK, sessionCookie));
|
||||
channel.readOutbound(); // Read and discard the response sent to the client.
|
||||
|
||||
// Simulate a subsequent client request and check for the registrar ID header.
|
||||
String clientRequestContent = "<epp>login</epp>";
|
||||
channel.writeInbound(Unpooled.wrappedBuffer(clientRequestContent.getBytes(UTF_8)));
|
||||
|
||||
FullHttpRequest relayedRequest = channel.readInbound();
|
||||
FullHttpRequest expectedRequest = makeEppHttpRequest(clientRequestContent, sessionCookie);
|
||||
expectedRequest.headers().set(ProxyHttpHeaders.REGISTRAR_ID, registrarId);
|
||||
|
||||
assertHttpRequestEquivalent(relayedRequest, expectedRequest);
|
||||
assertThat((Object) channel.readInbound()).isNull();
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_registrarIdHeader_isNotSetWhenSessionInfoCookieIsMissing() throws Exception {
|
||||
setHandshakeSuccess();
|
||||
channel.readInbound(); // Read and discard the initial hello request.
|
||||
|
||||
// Simulate a server response that does NOT set the SESSION_INFO cookie.
|
||||
Cookie otherCookie = new DefaultCookie("some_other_cookie", "some_value");
|
||||
channel.writeOutbound(
|
||||
makeEppHttpResponse("<epp>greeting</epp>", HttpResponseStatus.OK, otherCookie));
|
||||
channel.readOutbound(); // Read and discard the response sent to the client.
|
||||
|
||||
// Simulate a subsequent client request and verify the header is absent.
|
||||
String clientRequestContent = "<epp>login</epp>";
|
||||
channel.writeInbound(Unpooled.wrappedBuffer(clientRequestContent.getBytes(UTF_8)));
|
||||
|
||||
FullHttpRequest relayedRequest = channel.readInbound();
|
||||
FullHttpRequest expectedRequest = makeEppHttpRequest(clientRequestContent, otherCookie);
|
||||
|
||||
assertHttpRequestEquivalent(relayedRequest, expectedRequest);
|
||||
assertThat(relayedRequest.headers().contains(ProxyHttpHeaders.REGISTRAR_ID)).isFalse();
|
||||
assertThat((Object) channel.readInbound()).isNull();
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_registrarIdHeader_isNotSetWhenClientIdIsNull() throws Exception {
|
||||
setHandshakeSuccess();
|
||||
channel.readInbound(); // Read and discard the initial hello request.
|
||||
|
||||
// Simulate a server response with a SESSION_INFO cookie where clientId is "null".
|
||||
String sessionInfoValue =
|
||||
BaseEncoding.base64Url().encode("alpha,clientId=null,beta".getBytes(US_ASCII));
|
||||
Cookie sessionCookie = new DefaultCookie("SESSION_INFO", sessionInfoValue);
|
||||
channel.writeOutbound(
|
||||
makeEppHttpResponse("<epp>greeting</epp>", HttpResponseStatus.OK, sessionCookie));
|
||||
channel.readOutbound(); // Read and discard the response sent to the client.
|
||||
|
||||
// Simulate a subsequent client request and verify the header is absent.
|
||||
String clientRequestContent = "<epp>login</epp>";
|
||||
channel.writeInbound(Unpooled.wrappedBuffer(clientRequestContent.getBytes(UTF_8)));
|
||||
|
||||
FullHttpRequest relayedRequest = channel.readInbound();
|
||||
FullHttpRequest expectedRequest = makeEppHttpRequest(clientRequestContent, sessionCookie);
|
||||
|
||||
assertHttpRequestEquivalent(relayedRequest, expectedRequest);
|
||||
assertThat(relayedRequest.headers().contains(ProxyHttpHeaders.REGISTRAR_ID)).isFalse();
|
||||
assertThat((Object) channel.readInbound()).isNull();
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,9 +30,6 @@ public final class ProxyHttpHeaders {
|
||||
/** HTTP header name used to pass the client IP address from the proxy to Nomulus. */
|
||||
public static final String IP_ADDRESS = "Nomulus-Client-Address";
|
||||
|
||||
/** HTTP header name used to pass the Registrar Id from the proxy to Nomulus. */
|
||||
public static final String REGISTRAR_ID = "Nomulus-Registrar-Id";
|
||||
|
||||
/**
|
||||
* Fallback HTTP header name used to pass the client IP address from the proxy to Nomulus.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user