1
0
mirror of https://github.com/google/nomulus synced 2026-05-25 01:01:57 +00:00

Compare commits

...

11 Commits

Author SHA1 Message Date
Weimin Yu
85b588b51f Add a disposition header to email attachments (#2223)
This may help with the billing-team with attached invoices.

This is a standard header that should do no harm.
2023-11-16 13:31:12 -05:00
Pavlo Tkach
572b7101cb Create separate BSA service (#2221) 2023-11-15 18:38:26 -05:00
Weimin Yu
445825957d Bsa Persistence entity classes (#2205)
* Add persistence model object
2023-11-15 16:43:22 -05:00
Weimin Yu
7ab76f3573 Pin Flyway tool jar to 9.22.3 (#2222)
Flyway 10+ is not compatible with Java 8.

Rollback this change after we upgrade to Java 11.
2023-11-15 14:48:55 -05:00
Weimin Yu
9e3c58989a Add an IDN helper (#2217)
* Add an IDN helper

Add a helper that checks the validity of labels in IDNs.
All organizes TLDs according to the IDNs they support.
2023-11-10 19:55:04 -05:00
Lai Jiang
cf9c1ec7c3 Use Java 8 runtime on sandbox and production (#2218)
Java 17 injects unexpected headers to X-Forwarded-For, which causes
issues with validating incoming IP addresses.

This is a partial reversion of #2201. We are still keeping Java 17 in other environment but sandbox and production needs to be able to parse the header to accept incoming EPP connections from registrars. Once we fix it we will re-enable Java 17 in these environment.
2023-11-10 14:39:16 -05:00
Pavlo Tkach
69ea87be31 Add handler for Console API requests and XSRF token creation and verification (#2211) 2023-11-09 17:51:53 -05:00
Lai Jiang
779d0c9d37 Add a fallback token verifier (#2216)
This allows us to switch the proxy to a different client ID without
disrupting the service. This is a temporary measure and will be removed
once the switch is complete.
2023-11-09 16:05:14 -05:00
Weimin Yu
2855944214 Add TLD BSA enroll start date to schema (#2215)
Also adds a placeholder getter in the Tld class, so that it can be
mocked/spied in tests. This way more BSA related code can be submitted
before the schema is deployed to prod.
2023-11-09 13:52:45 -05:00
Ben McIlwain
992d1c1349 Reduce the QPS of the refresh DNS for all domains action (#2212)
This also adds a targeted QPS as a parameter in case we need to manually bump it
up (or down) for some reason without having to make code changes and re-deploy.
2023-11-08 13:47:37 -05:00
Pavlo Tkach
f50290ce1d Add static IP connector to crash and alpha configs (#2213) 2023-11-08 13:26:32 -05:00
79 changed files with 3987 additions and 2246 deletions

View File

@@ -347,6 +347,7 @@ subprojects {
def services = [':services:default',
':services:backend',
':services:bsa',
':services:tools',
':services:pubapi']

View File

@@ -0,0 +1,21 @@
// Copyright 2023 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.
package google.registry.bsa;
/** Identifiers of the BSA lists with blocking labels. */
public enum BlockList {
BLOCK,
BLOCK_PLUS;
}

View File

@@ -0,0 +1,20 @@
// Copyright 2023 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.
package google.registry.bsa;
/** The processing stages of a download. */
public enum DownloadStage {
DOWNLOAD;
}

View File

@@ -0,0 +1,108 @@
// Copyright 2023 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.
package google.registry.bsa;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Maps.transformValues;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import google.registry.model.tld.Tld;
import google.registry.model.tld.Tld.TldType;
import google.registry.model.tld.Tlds;
import google.registry.tldconfig.idn.IdnLabelValidator;
import google.registry.tldconfig.idn.IdnTableEnum;
import google.registry.util.Clock;
import javax.inject.Inject;
import org.joda.time.DateTime;
/**
* Checks labels' validity wrt Idns in TLDs enrolled with BSA.
*
* <p>Each instance takes a snapshot of the TLDs at instantiation time, and should be limited to the
* Request scope.
*/
public class IdnChecker {
private static final IdnLabelValidator IDN_LABEL_VALIDATOR = new IdnLabelValidator();
private final ImmutableMap<IdnTableEnum, ImmutableSet<Tld>> idnToTlds;
private final ImmutableSet<Tld> allTlds;
@Inject
IdnChecker(Clock clock) {
this.idnToTlds = getIdnToTldMap(clock.nowUtc());
allTlds = idnToTlds.values().stream().flatMap(ImmutableSet::stream).collect(toImmutableSet());
}
// TODO(11/30/2023): Remove below when new Tld schema is deployed and the `getBsaEnrollStartTime`
// method is no longer hardcoded.
@VisibleForTesting
IdnChecker(ImmutableMap<IdnTableEnum, ImmutableSet<Tld>> idnToTlds) {
this.idnToTlds = idnToTlds;
allTlds = idnToTlds.values().stream().flatMap(ImmutableSet::stream).collect(toImmutableSet());
}
/** Returns all IDNs in which the {@code label} is valid. */
ImmutableSet<IdnTableEnum> getAllValidIdns(String label) {
return idnToTlds.keySet().stream()
.filter(idnTable -> idnTable.getTable().isValidLabel(label))
.collect(toImmutableSet());
}
/**
* Returns the TLDs that support at least one IDN in the {@code idnTables}.
*
* @param idnTables String names of {@link IdnTableEnum} values
*/
public ImmutableSet<Tld> getSupportingTlds(ImmutableSet<String> idnTables) {
return idnTables.stream()
.map(IdnTableEnum::valueOf)
.filter(idnToTlds::containsKey)
.map(idnToTlds::get)
.flatMap(ImmutableSet::stream)
.collect(toImmutableSet());
}
/**
* Returns the TLDs that do not support any IDN in the {@code idnTables}.
*
* @param idnTables String names of {@link IdnTableEnum} values
*/
public SetView<Tld> getForbiddingTlds(ImmutableSet<String> idnTables) {
return Sets.difference(allTlds, getSupportingTlds(idnTables));
}
private static boolean isEnrolledWithBsa(Tld tld, DateTime now) {
DateTime enrollTime = tld.getBsaEnrollStartTime();
return enrollTime != null && enrollTime.isBefore(now);
}
private static ImmutableMap<IdnTableEnum, ImmutableSet<Tld>> getIdnToTldMap(DateTime now) {
ImmutableMultimap.Builder<IdnTableEnum, Tld> idnToTldMap = new ImmutableMultimap.Builder();
Tlds.getTldEntitiesOfType(TldType.REAL).stream()
.filter(tld -> isEnrolledWithBsa(tld, now))
.forEach(
tld -> {
for (IdnTableEnum idn : IDN_LABEL_VALIDATOR.getIdnTablesForTld(tld)) {
idnToTldMap.put(idn, tld);
}
});
return ImmutableMap.copyOf(transformValues(idnToTldMap.build().asMap(), ImmutableSet::copyOf));
}
}

View File

@@ -0,0 +1,45 @@
// Copyright 2023 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.
package google.registry.bsa;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import google.registry.request.Action;
import google.registry.request.Action.Service;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import javax.inject.Inject;
@Action(
service = Service.BSA,
path = PlaceholderAction.PATH,
method = Action.Method.GET,
auth = Auth.AUTH_API_ADMIN)
public class PlaceholderAction implements Runnable {
private final Response response;
static final String PATH = "/_dr/task/bsaDownload";
@Inject
public PlaceholderAction(Response response) {
this.response = response;
}
@Override
public void run() {
response.setStatus(SC_OK);
response.setPayload("Hello World");
}
}

View File

@@ -0,0 +1,102 @@
// Copyright 2023 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.
package google.registry.bsa.persistence;
import com.google.common.base.Objects;
import google.registry.bsa.persistence.BsaDomainInUse.BsaDomainInUseId;
import google.registry.model.CreateAutoTimestamp;
import google.registry.persistence.VKey;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.IdClass;
/** A domain matching a BSA label but is in use (registered or reserved), so cannot be blocked. */
@Entity
@IdClass(BsaDomainInUseId.class)
public class BsaDomainInUse {
@Id String label;
@Id String tld;
@Column(nullable = false)
@Enumerated(EnumType.STRING)
Reason reason;
/**
* Creation time of this record, which is the most recent time when the domain was detected to be
* in use wrt BSA. It may be during the processing of a download, or during some other job that
* refreshes the state.
*
* <p>This field is for information only.
*/
@SuppressWarnings("unused")
@Column(nullable = false)
CreateAutoTimestamp createTime = CreateAutoTimestamp.create(null);
// For Hibernate
BsaDomainInUse() {}
public BsaDomainInUse(String label, String tld, Reason reason) {
this.label = label;
this.tld = tld;
this.reason = reason;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof BsaDomainInUse)) {
return false;
}
BsaDomainInUse that = (BsaDomainInUse) o;
return Objects.equal(label, that.label)
&& Objects.equal(tld, that.tld)
&& reason == that.reason
&& Objects.equal(createTime, that.createTime);
}
@Override
public int hashCode() {
return Objects.hashCode(label, tld, reason, createTime);
}
enum Reason {
REGISTERED,
RESERVED;
}
static class BsaDomainInUseId implements Serializable {
private String label;
private String tld;
// For Hibernate
BsaDomainInUseId() {}
BsaDomainInUseId(String label, String tld) {
this.label = label;
this.tld = tld;
}
}
static VKey<BsaDomainInUse> vKey(String label, String tld) {
return VKey.create(BsaDomainInUse.class, new BsaDomainInUseId(label, tld));
}
}

View File

@@ -0,0 +1,131 @@
// Copyright 2023 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.
package google.registry.bsa.persistence;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static google.registry.bsa.DownloadStage.DOWNLOAD;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.bsa.BlockList;
import google.registry.bsa.DownloadStage;
import google.registry.model.CreateAutoTimestamp;
import google.registry.model.UpdateAutoTimestamp;
import google.registry.persistence.VKey;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.Table;
import org.joda.time.DateTime;
/** Records of ongoing and completed download jobs. */
@Entity
@Table(indexes = {@Index(columnList = "creationTime")})
public class BsaDownload {
private static final Joiner CSV_JOINER = Joiner.on(',');
private static final Splitter CSV_SPLITTER = Splitter.on(',');
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long jobId;
@Column(nullable = false)
CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null);
@Column(nullable = false)
UpdateAutoTimestamp updateTime = UpdateAutoTimestamp.create(null);
@Column(nullable = false)
String blockListChecksums = "";
@Column(nullable = false)
@Enumerated(EnumType.STRING)
DownloadStage stage = DOWNLOAD;
BsaDownload() {}
public long getJobId() {
return jobId;
}
/**
* Returns the starting time of this job as a string, which can be used as folder name on GCS when
* storing download data.
*/
public String getJobName() {
return creationTime.getTimestamp().toString();
}
public DownloadStage getStage() {
return this.stage;
}
BsaDownload setStage(DownloadStage stage) {
this.stage = stage;
return this;
}
DateTime getCreationTime() {
return creationTime.getTimestamp();
}
BsaDownload setBlockListChecksums(ImmutableMap<BlockList, String> checksums) {
blockListChecksums =
CSV_JOINER.withKeyValueSeparator("=").join(ImmutableSortedMap.copyOf(checksums));
return this;
}
ImmutableMap<BlockList, String> getChecksums() {
if (blockListChecksums.isEmpty()) {
return ImmutableMap.of();
}
return CSV_SPLITTER.withKeyValueSeparator('=').split(blockListChecksums).entrySet().stream()
.collect(
toImmutableMap(entry -> BlockList.valueOf(entry.getKey()), entry -> entry.getValue()));
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof BsaDownload)) {
return false;
}
BsaDownload that = (BsaDownload) o;
return Objects.equal(creationTime, that.creationTime)
&& Objects.equal(updateTime, that.updateTime)
&& Objects.equal(blockListChecksums, that.blockListChecksums)
&& stage == that.stage;
}
@Override
public int hashCode() {
return Objects.hashCode(creationTime, updateTime, blockListChecksums, stage);
}
static VKey<BsaDownload> vKey(long jobId) {
return VKey.create(BsaDownload.class, Long.valueOf(jobId));
}
}

View File

@@ -0,0 +1,79 @@
// Copyright 2023 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.
package google.registry.bsa.persistence;
import com.google.common.base.Objects;
import google.registry.persistence.VKey;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.joda.time.DateTime;
/**
* Specifies a second-level TLD name that should be blocked from registration in all TLDs except by
* the label's owner.
*
* <p>The label is valid (wrt IDN) in at least one TLD.
*/
@Entity
public final class BsaLabel {
@Id String label;
/**
* Creation time of this label. This field is for human use, and should give the name of the GCS
* folder that contains the downloaded BSA data.
*
* <p>See {@link BsaDownload#getCreationTime} and {@link BsaDownload#getJobName} for more
* information.
*/
@SuppressWarnings("unused")
@Column(nullable = false)
DateTime creationTime;
// For Hibernate.
BsaLabel() {}
BsaLabel(String label, DateTime creationTime) {
this.label = label;
this.creationTime = creationTime;
}
/** Returns the label to be blocked. */
public String getLabel() {
return label;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof BsaLabel)) {
return false;
}
BsaLabel label1 = (BsaLabel) o;
return Objects.equal(label, label1.label) && Objects.equal(creationTime, label1.creationTime);
}
@Override
public int hashCode() {
return Objects.hashCode(label, creationTime);
}
static VKey<BsaLabel> vKey(String label) {
return VKey.create(BsaLabel.class, label);
}
}

View File

@@ -1192,6 +1192,12 @@ public final class RegistryConfig {
return config.auth.oauthClientId;
}
@Provides
@Config("fallbackOauthClientId")
public static String provideFallbackOauthClientId(RegistryConfigSettings config) {
return config.auth.fallbackOauthClientId;
}
/**
* Provides the OAuth scopes required for accessing Google APIs using the default credential.
*/
@@ -1452,6 +1458,15 @@ public final class RegistryConfig {
return makeUrl(CONFIG_SETTINGS.get().gcpProject.backendServiceUrl);
}
/**
* Returns the address of the Nomulus app bsa HTTP server.
*
* <p>This is used by the {@code nomulus} tool to connect to the App Engine remote API.
*/
public static URL getBsaServer() {
return makeUrl(CONFIG_SETTINGS.get().gcpProject.bsaServiceUrl);
}
/**
* Returns the address of the Nomulus app tools HTTP server.
*

View File

@@ -53,6 +53,7 @@ public class RegistryConfigSettings {
public boolean isLocal;
public String defaultServiceUrl;
public String backendServiceUrl;
public String bsaServiceUrl;
public String toolsServiceUrl;
public String pubapiServiceUrl;
}
@@ -61,6 +62,7 @@ public class RegistryConfigSettings {
public static class Auth {
public List<String> allowedServiceAccountEmails;
public String oauthClientId;
public String fallbackOauthClientId;
}
/** Configuration options for accessing Google APIs. */

View File

@@ -20,9 +20,11 @@ gcpProject:
# URLs of the services for the project.
defaultServiceUrl: https://default.example.com
backendServiceUrl: https://backend.example.com
bsaServiceUrl: https://bsa.example.com
toolsServiceUrl: https://tools.example.com
pubapiServiceUrl: https://pubapi.example.com
gSuite:
# Publicly accessible domain name of the running G Suite instance.
domainName: domain-registry.example
@@ -321,6 +323,10 @@ auth:
# the same as this one.
oauthClientId: iap-oauth-clientid
# Same as above, but serve as a fallback, so we can switch the client ID of
# the proxy without downtime.
fallbackOauthClientId: fallback-oauth-clientid
credentialOAuth:
# OAuth scopes required for accessing Google APIs using the default
# credential.

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java17</runtime>
<service>bsa</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
<max-instances>100</max-instances>
<idle-timeout>10m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="alpha"/>
</system-properties>
<!-- Enable external traffic to go through VPC, required for static ip -->
<vpc-access-connector>
<name>projects/domain-registry-alpha/locations/us-central1/connectors/appengine-connector</name>
<egress-setting>all-traffic</egress-setting>
</vpc-access-connector>
<static-files>
<include path="/*.html" expiration="1m"/>
</static-files>
</appengine-web-app>

View File

@@ -31,6 +31,12 @@ encoding="UTF-8"?>
<context-root>backend</context-root>
</web>
</module>
<module>
<web>
<web-uri>bsa</web-uri>
<context-root>bsa</context-root>
</web>
</module>
<module>
<web>
<web-uri>tools</web-uri>

View File

@@ -0,0 +1,17 @@
# A default java.util.logging configuration.
# (All App Engine logging is through java.util.logging by default).
#
# To use this configuration, copy it into your application's WEB-INF
# folder and add the following to your appengine-web.xml:
#
# <system-properties>
# <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
# </system-properties>
#
# Set the default logging level for all loggers to INFO.
.level = INFO
# Turn off logging in Hibernate classes for misleading ERROR-level logs
org.hibernate.engine.jdbc.batch.internal.BatchingBatch.level=OFF
org.hibernate.engine.jdbc.spi.SqlExceptionHelper.level=OFF

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- Servlets -->
<!-- Servlet for injected backends actions -->
<servlet>
<display-name>BsaServlet</display-name>
<servlet-name>bsa-servlet</servlet-name>
<servlet-class>google.registry.module.bsa.BsaServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Test action -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/bsa</url-pattern>
</servlet-mapping>
<!-- Security config -->
<security-constraint>
<web-resource-collection>
<web-resource-name>Internal</web-resource-name>
<description>
Admin-only internal section. Requests for paths covered by the URL patterns below will be
checked for a logged-in user account that's allowed to access the AppEngine admin console
(NOTE: this includes Editor/Viewer permissions in addition to Owner and the new IAM
App Engine Admin role. See https://cloud.google.com/appengine/docs/java/access-control
specifically the "Access handlers that have a login:admin restriction" line.)
TODO(b/28219927): lift some of these restrictions so that we can allow OAuth authentication
for endpoints that need to be accessed by open-source automated processes.
</description>
<!-- Internal AppEngine endpoints. The '_ah' is short for app hosting. -->
<url-pattern>/_ah/*</url-pattern>
<!-- Registrar console (should not be available on non-default module). -->
<url-pattern>/registrar*</url-pattern>
<!-- Verbatim JavaScript sources (only visible to admins for debugging). -->
<url-pattern>/assets/sources/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
<!-- Repeated here since catch-all rule below is not inherited. -->
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<!-- Require TLS on all requests. -->
<security-constraint>
<web-resource-collection>
<web-resource-name>Secure</web-resource-name>
<description>
Require encryption for all paths. http URLs will be redirected to https.
</description>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
</web-app>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java17</runtime>
<service>bsa</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
<max-instances>10</max-instances>
<idle-timeout>10m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="crash"/>
</system-properties>
<!-- Enable external traffic to go through VPC, required for static ip -->
<vpc-access-connector>
<name>projects/domain-registry-crash/locations/us-central1/connectors/appengine-connector</name>
<egress-setting>all-traffic</egress-setting>
</vpc-access-connector>
<static-files>
<include path="/*.html" expiration="1m"/>
</static-files>
</appengine-web-app>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java17</runtime>
<service>bsa</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
<max-instances>10</max-instances>
<idle-timeout>10m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="local"/>
<property name="appengine.generated.dir"
value="/tmp/domain-registry-appengine-generated/local/"/>
</system-properties>
<static-files>
<include path="/*.html">
<http-header name="Cache-Control" value="max-age=0,must-revalidate" />
</include>
</static-files>
</appengine-web-app>

View File

@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java17</runtime>
<runtime>java8</runtime>
<service>backend</service>
<app-engine-apis>true</app-engine-apis>
<!--app-engine-apis>true</app-engine-apis-->
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<basic-scaling>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<service>bsa</service>
<!--app-engine-apis>true</app-engine-apis-->
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<basic-scaling>
<max-instances>100</max-instances>
<idle-timeout>10m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="production"/>
</system-properties>
<static-files>
<include path="/*.html" expiration="1d"/>
</static-files>
<!-- Prevent uncaught servlet errors from leaking a stack trace. -->
<static-error-handlers>
<handler file="error.html"/>
</static-error-handlers>
</appengine-web-app>

View File

@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java17</runtime>
<runtime>java8</runtime>
<service>default</service>
<app-engine-apis>true</app-engine-apis>
<!--app-engine-apis>true</app-engine-apis-->
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<manual-scaling>

View File

@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java17</runtime>
<runtime>java8</runtime>
<service>pubapi</service>
<app-engine-apis>true</app-engine-apis>
<!--app-engine-apis>true</app-engine-apis-->
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<manual-scaling>

View File

@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java17</runtime>
<runtime>java8</runtime>
<service>tools</service>
<app-engine-apis>true</app-engine-apis>
<!--app-engine-apis>true</app-engine-apis-->
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<basic-scaling>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java17</runtime>
<service>bsa</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
<max-instances>10</max-instances>
<idle-timeout>10m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="qa"/>
</system-properties>
<static-files>
<include path="/*.html" expiration="1h"/>
</static-files>
<!-- Enable external traffic to go through VPC, required for static ip -->
<vpc-access-connector>
<name>projects/domain-registry-qa/locations/us-central1/connectors/appengine-connector</name>
<egress-setting>all-traffic</egress-setting>
</vpc-access-connector>
<!-- Prevent uncaught servlet errors from leaking a stack trace. -->
<static-error-handlers>
<handler file="error.html"/>
</static-error-handlers>
</appengine-web-app>

View File

@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java17</runtime>
<runtime>java8</runtime>
<service>backend</service>
<app-engine-apis>true</app-engine-apis>
<!--app-engine-apis>true</app-engine-apis-->
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
@@ -22,12 +23,6 @@
<include path="/*.html" expiration="1d"/>
</static-files>
<!-- Enable external traffic to go through VPC, required for static ip -->
<vpc-access-connector>
<name>projects/domain-registry-sandbox/locations/us-central1/connectors/appengine-connector</name>
<egress-setting>all-traffic</egress-setting>
</vpc-access-connector>
<!-- Prevent uncaught servlet errors from leaking a stack trace. -->
<static-error-handlers>
<handler file="error.html"/>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<service>bsa</service>
<!--app-engine-apis>true</app-engine-apis-->
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
<max-instances>100</max-instances>
<idle-timeout>10m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="sandbox"/>
</system-properties>
<static-files>
<include path="/*.html" expiration="1d"/>
</static-files>
<!-- Enable external traffic to go through VPC, required for static ip -->
<vpc-access-connector>
<name>projects/domain-registry-sandbox/locations/us-central1/connectors/appengine-connector</name>
<egress-setting>all-traffic</egress-setting>
</vpc-access-connector>
<!-- Prevent uncaught servlet errors from leaking a stack trace. -->
<static-error-handlers>
<handler file="error.html"/>
</static-error-handlers>
</appengine-web-app>

View File

@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java17</runtime>
<runtime>java8</runtime>
<service>default</service>
<app-engine-apis>true</app-engine-apis>
<!--app-engine-apis>true</app-engine-apis-->
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<manual-scaling>

View File

@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java17</runtime>
<runtime>java8</runtime>
<service>pubapi</service>
<app-engine-apis>true</app-engine-apis>
<!--app-engine-apis>true</app-engine-apis-->
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<manual-scaling>

View File

@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java17</runtime>
<runtime>java8</runtime>
<service>tools</service>
<app-engine-apis>true</app-engine-apis>
<!--app-engine-apis>true</app-engine-apis-->
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>

View File

@@ -138,6 +138,7 @@ public final class GmailClient {
BodyPart attachmentPart = new MimeBodyPart();
attachmentPart.setContent(attachment.content(), attachment.contentType().toString());
attachmentPart.setFileName(attachment.filename());
attachmentPart.setDisposition(MimeBodyPart.ATTACHMENT);
multipart.addBodyPart(attachmentPart);
}
msg.addRecipients(RecipientType.BCC, toArray(emailMessage.bccs(), Address.class));

View File

@@ -550,6 +550,10 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
@JsonSerialize(using = SortedEnumSetSerializer.class)
Set<IdnTableEnum> idnTables;
// TODO(11/30/2023): uncomment below two lines
// /** The start time of this TLD's enrollment in the BSA program, if applicable. */
// @JsonIgnore @Nullable DateTime bsaEnrollStartTime;
public String getTldStr() {
return tldStr;
}
@@ -569,6 +573,15 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
return tldType;
}
/** Returns the time when this TLD was enrolled in the Brand Safety Alliance (BSA) program. */
@JsonIgnore // Annotation can be removed once we add the field and annotate it.
@Nullable
public DateTime getBsaEnrollStartTime() {
// TODO(11/30/2023): uncomment below.
// return this.bsaEnrollStartTime;
return null;
}
/** Retrieve whether invoicing is enabled. */
public boolean isInvoicingEnabled() {
return invoicingEnabled;
@@ -939,6 +952,7 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
}
public Builder setReservedListsByName(Set<String> reservedListNames) {
// TODO(b/309175133): forbid if enrolled with BSA
checkArgument(reservedListNames != null, "reservedListNames must not be null");
ImmutableSet.Builder<ReservedList> builder = new ImmutableSet.Builder<>();
for (String reservedListName : reservedListNames) {
@@ -958,6 +972,7 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
}
public Builder setReservedLists(Set<ReservedList> reservedLists) {
// TODO(b/309175133): forbid if enrolled with BSA
checkArgumentNotNull(reservedLists, "reservedLists must not be null");
ImmutableSet.Builder<String> nameBuilder = new ImmutableSet.Builder<>();
for (ReservedList reservedList : reservedLists) {
@@ -1076,6 +1091,7 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
}
public Builder setIdnTables(ImmutableSet<IdnTableEnum> idnTables) {
// TODO(b/309175133): forbid if enrolled with BSA.
getInstance().idnTables = idnTables;
return this;
}
@@ -1085,6 +1101,13 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
return this;
}
public Builder setBsaEnrollStartTime(DateTime enrollTime) {
// TODO(b/309175133): forbid if enrolled with BSA
// TODO(11/30/2023): uncomment below line
// getInstance().bsaEnrollStartTime = enrollTime;
return this;
}
@Override
public Tld build() {
final Tld instance = getInstance();

View File

@@ -0,0 +1,46 @@
// Copyright 2023 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.
package google.registry.module.bsa;
import com.google.monitoring.metrics.MetricReporter;
import dagger.Component;
import dagger.Lazy;
import google.registry.config.CredentialModule;
import google.registry.config.RegistryConfig.ConfigModule;
import google.registry.module.bsa.BsaRequestComponent.BsaRequestComponentModule;
import google.registry.monitoring.whitebox.StackdriverModule;
import google.registry.request.Modules.GsonModule;
import google.registry.request.Modules.UserServiceModule;
import google.registry.request.auth.AuthModule;
import google.registry.util.UtilsModule;
import javax.inject.Singleton;
@Singleton
@Component(
modules = {
AuthModule.class,
UtilsModule.class,
UserServiceModule.class,
GsonModule.class,
ConfigModule.class,
StackdriverModule.class,
CredentialModule.class,
BsaRequestComponentModule.class
})
interface BsaComponent {
BsaRequestHandler requestHandler();
Lazy<MetricReporter> metricReporter();
}

View File

@@ -0,0 +1,45 @@
// Copyright 2023 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.
package google.registry.module.bsa;
import dagger.Module;
import dagger.Subcomponent;
import google.registry.bsa.PlaceholderAction;
import google.registry.request.RequestComponentBuilder;
import google.registry.request.RequestModule;
import google.registry.request.RequestScope;
@RequestScope
@Subcomponent(
modules = {
RequestModule.class,
})
interface BsaRequestComponent {
PlaceholderAction bsaAction();
@Subcomponent.Builder
abstract class Builder implements RequestComponentBuilder<BsaRequestComponent> {
@Override
public abstract Builder requestModule(RequestModule requestModule);
@Override
public abstract BsaRequestComponent build();
}
@Module(subcomponents = BsaRequestComponent.class)
class BsaRequestComponentModule {}
}

View File

@@ -0,0 +1,29 @@
// Copyright 2023 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.
package google.registry.module.bsa;
import google.registry.request.RequestHandler;
import google.registry.request.auth.RequestAuthenticator;
import javax.inject.Inject;
import javax.inject.Provider;
public class BsaRequestHandler extends RequestHandler<BsaRequestComponent> {
@Inject
public BsaRequestHandler(
Provider<BsaRequestComponent.Builder> componentBuilderProvider,
RequestAuthenticator requestAuthenticator) {
super(componentBuilderProvider, requestAuthenticator);
}
}

View File

@@ -0,0 +1,30 @@
// Copyright 2023 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.
package google.registry.module.bsa;
import com.google.monitoring.metrics.MetricReporter;
import dagger.Lazy;
import google.registry.module.ServletBase;
public final class BsaServlet extends ServletBase {
private static final BsaComponent component = DaggerBsaComponent.create();
private static final BsaRequestHandler requestHandler = component.requestHandler();
private static final Lazy<MetricReporter> metricReporter = component.metricReporter();
public BsaServlet() {
super(requestHandler, metricReporter);
}
}

View File

@@ -0,0 +1,16 @@
// Copyright 2023 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.
@javax.annotation.ParametersAreNonnullByDefault
package google.registry.module.bsa;

View File

@@ -39,7 +39,7 @@ import javax.persistence.PersistenceException;
* <p>See the {@code logging.properties} files in the {@code env} package for the specific Hibernate
* classes that have logging suppressed.
*/
class DatabaseException extends PersistenceException {
public class DatabaseException extends PersistenceException {
private transient String cachedMessage;

View File

@@ -30,11 +30,13 @@ public @interface Action {
/** App Engine services supported by the request processor. */
enum Service {
BSA("bsa"),
DEFAULT("default"),
TOOLS("tools"),
BACKEND("backend"),
PUBAPI("pubapi");
private final String serviceId;
Service(String serviceId) {

View File

@@ -15,6 +15,7 @@
package google.registry.request;
import com.google.common.net.MediaType;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.joda.time.DateTime;
@@ -51,4 +52,11 @@ public interface Response {
* @see HttpServletResponse#setDateHeader(String, long)
*/
void setDateHeader(String header, DateTime timestamp);
/**
* Adds a cookie to the response
*
* @see HttpServletResponse#addCookie(Cookie)
*/
void addCookie(Cookie cookie);
}

View File

@@ -17,6 +17,7 @@ package google.registry.request;
import com.google.common.net.MediaType;
import java.io.IOException;
import javax.inject.Inject;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.joda.time.DateTime;
@@ -58,4 +59,9 @@ public final class ResponseImpl implements Response {
public void setDateHeader(String header, DateTime timestamp) {
rsp.setDateHeader(header, timestamp.getMillis());
}
@Override
public void addCookie(Cookie cookie) {
rsp.addCookie(cookie);
}
}

View File

@@ -55,6 +55,9 @@ public class AuthModule {
@Qualifier
@interface RegularOidc {}
@Qualifier
@interface RegularOidcFallback {}
@Provides
@IapOidc
@Singleton
@@ -71,6 +74,14 @@ public class AuthModule {
return TokenVerifier.newBuilder().setAudience(clientId).setIssuer(REGULAR_ISSUER_URL).build();
}
@Provides
@RegularOidcFallback
@Singleton
TokenVerifier provideFallbackRegularTokenVerifier(
@Config("fallbackOauthClientId") String clientId) {
return TokenVerifier.newBuilder().setAudience(clientId).setIssuer(REGULAR_ISSUER_URL).build();
}
@Provides
@IapOidc
@Singleton

View File

@@ -25,6 +25,7 @@ import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.request.auth.AuthModule.IapOidc;
import google.registry.request.auth.AuthModule.RegularOidc;
import google.registry.request.auth.AuthModule.RegularOidcFallback;
import google.registry.request.auth.AuthSettings.AuthLevel;
import java.util.Optional;
import javax.annotation.Nullable;
@@ -53,6 +54,8 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
protected final TokenVerifier tokenVerifier;
protected final Optional<TokenVerifier> fallbackTokenVerifier;
protected final TokenExtractor tokenExtractor;
private final ImmutableSet<String> serviceAccountEmails;
@@ -60,9 +63,11 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
protected OidcTokenAuthenticationMechanism(
ImmutableSet<String> serviceAccountEmails,
TokenVerifier tokenVerifier,
@Nullable TokenVerifier fallbackTokenVerifier,
TokenExtractor tokenExtractor) {
this.serviceAccountEmails = serviceAccountEmails;
this.tokenVerifier = tokenVerifier;
this.fallbackTokenVerifier = Optional.ofNullable(fallbackTokenVerifier);
this.tokenExtractor = tokenExtractor;
}
@@ -77,7 +82,7 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
if (rawIdToken == null) {
return AuthResult.NOT_AUTHENTICATED;
}
JsonWebSignature token;
JsonWebSignature token = null;
try {
token = tokenVerifier.verify(rawIdToken);
} catch (Exception e) {
@@ -86,8 +91,25 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
RegistryEnvironment.get().equals(RegistryEnvironment.PRODUCTION)
? "Raw token redacted in prod"
: rawIdToken);
return AuthResult.NOT_AUTHENTICATED;
}
if (token == null) {
if (fallbackTokenVerifier.isPresent()) {
try {
token = fallbackTokenVerifier.get().verify(rawIdToken);
} catch (Exception e) {
logger.atInfo().withCause(e).log(
"Failed OIDC fallback verification attempt:\n%s",
RegistryEnvironment.get().equals(RegistryEnvironment.PRODUCTION)
? "Raw token redacted in prod"
: rawIdToken);
return AuthResult.NOT_AUTHENTICATED;
}
} else {
return AuthResult.NOT_AUTHENTICATED;
}
}
String email = (String) token.getPayload().get("email");
if (email == null) {
logger.atWarning().log("No email address from the OIDC token:\n%s", token.getPayload());
@@ -141,7 +163,7 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
@Config("allowedServiceAccountEmails") ImmutableSet<String> serviceAccountEmails,
@IapOidc TokenVerifier tokenVerifier,
@IapOidc TokenExtractor tokenExtractor) {
super(serviceAccountEmails, tokenVerifier, tokenExtractor);
super(serviceAccountEmails, tokenVerifier, null, tokenExtractor);
}
}
@@ -161,8 +183,9 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
protected RegularOidcAuthenticationMechanism(
@Config("allowedServiceAccountEmails") ImmutableSet<String> serviceAccountEmails,
@RegularOidc TokenVerifier tokenVerifier,
@RegularOidcFallback TokenVerifier fallbackTokenVerifier,
@RegularOidc TokenExtractor tokenExtractor) {
super(serviceAccountEmails, tokenVerifier, tokenExtractor);
super(serviceAccountEmails, tokenVerifier, fallbackTokenVerifier, tokenExtractor);
}
}
}

View File

@@ -34,7 +34,7 @@ import org.joda.time.Duration;
/** Helper class for generating and validate XSRF tokens. */
public final class XsrfTokenManager {
/** HTTP header used for transmitting XSRF tokens. */
/** HTTP header or cookie name used for transmitting XSRF tokens. */
public static final String X_CSRF_TOKEN = "X-CSRF-Token";
/** POST parameter used for transmitting XSRF tokens. */

View File

@@ -38,9 +38,7 @@ public final class IdnLabelValidator {
public Optional<String> findValidIdnTableForTld(String label, String tldStr) {
String unicodeString = Idn.toUnicode(label);
Tld tld = Tld.get(tldStr); // uses the cache
ImmutableSet<IdnTableEnum> idnTablesForTld = tld.getIdnTables();
ImmutableSet<IdnTableEnum> idnTables =
idnTablesForTld.isEmpty() ? DEFAULT_IDN_TABLES : idnTablesForTld;
ImmutableSet<IdnTableEnum> idnTables = getIdnTablesForTld(tld);
for (IdnTableEnum idnTable : idnTables) {
if (idnTable.getTable().isValidLabel(unicodeString)) {
return Optional.of(idnTable.getTable().getName());
@@ -48,4 +46,10 @@ public final class IdnLabelValidator {
}
return Optional.empty();
}
/** Returns the names of the IDN tables supported by a {@code tld}. */
public ImmutableSet<IdnTableEnum> getIdnTablesForTld(Tld tld) {
ImmutableSet<IdnTableEnum> idnTablesForTld = tld.getIdnTables();
return idnTablesForTld.isEmpty() ? DEFAULT_IDN_TABLES : idnTablesForTld;
}
}

View File

@@ -84,7 +84,7 @@ public final class IdnTable {
* Returns true if the given label is valid for this IDN table. A label is considered valid if all
* of its codepoints are in the IDN table.
*/
boolean isValidLabel(String label) {
public boolean isValidLabel(String label) {
final int length = label.length();
for (int i = 0; i < length; ) {
int codepoint = label.codePointAt(i);

View File

@@ -170,6 +170,8 @@ public class ServiceConnection {
return RegistryConfig.getToolsServer();
case BACKEND:
return RegistryConfig.getBackendServer();
case BSA:
return RegistryConfig.getBsaServer();
case PUBAPI:
return RegistryConfig.getPubapiServer();
}

View File

@@ -58,13 +58,25 @@ public class RefreshDnsForAllDomainsAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/** The number of DNS updates to enqueue per transaction. */
private static final int DEFAULT_BATCH_SIZE = 250;
/**
* The default number of DNS updates it is safe to execute per minute.
*
* <p>This is mostly a guess based on existing system performance, but the point is to be on the
* safe side and not cause contention with ongoing DNS updates from clients.
*/
private static final int DEFAULT_REFRESH_QPS = 7;
private final Response response;
private final ImmutableSet<String> tlds;
// Recommended value for batch size is between 200 and 500
private final int batchSize;
private final int refreshQps;
private final Random random;
@Inject
@@ -72,10 +84,12 @@ public class RefreshDnsForAllDomainsAction implements Runnable {
Response response,
@Parameter(PARAM_TLDS) ImmutableSet<String> tlds,
@Parameter("batchSize") Optional<Integer> batchSize,
@Parameter("refreshQps") Optional<Integer> refreshQps,
Random random) {
this.response = response;
this.tlds = tlds;
this.batchSize = batchSize.orElse(DEFAULT_BATCH_SIZE);
this.refreshQps = refreshQps.orElse(DEFAULT_REFRESH_QPS);
this.random = random;
}
@@ -83,7 +97,7 @@ public class RefreshDnsForAllDomainsAction implements Runnable {
public void run() {
assertTldsExist(tlds);
checkArgument(batchSize > 0, "Must specify a positive number for batch size");
int smearMinutes = tm().transact(this::calculateSmearMinutes, TRANSACTION_REPEATABLE_READ);
Duration smear = tm().transact(this::calculateSmear, TRANSACTION_REPEATABLE_READ);
ImmutableList<String> domainsBatch;
@Nullable String lastInPreviousBatch = null;
@@ -91,17 +105,16 @@ public class RefreshDnsForAllDomainsAction implements Runnable {
Optional<String> lastInPreviousBatchOpt = Optional.ofNullable(lastInPreviousBatch);
domainsBatch =
tm().transact(
() -> refreshBatch(lastInPreviousBatchOpt, smearMinutes),
TRANSACTION_REPEATABLE_READ);
() -> refreshBatch(lastInPreviousBatchOpt, smear), TRANSACTION_REPEATABLE_READ);
lastInPreviousBatch = domainsBatch.isEmpty() ? null : getLast(domainsBatch);
} while (domainsBatch.size() == batchSize);
}
/**
* Calculates the number of smear minutes to enqueue refreshes so that the DNS queue does not get
* Calculates the smear duration to enqueue refreshes so that the DNS queue does not get
* overloaded.
*/
private int calculateSmearMinutes() {
private Duration calculateSmear() {
Long activeDomains =
tm().query(
"SELECT COUNT(*) FROM Domain WHERE tld IN (:tlds) AND deletionTime = :endOfTime",
@@ -109,7 +122,7 @@ public class RefreshDnsForAllDomainsAction implements Runnable {
.setParameter("tlds", tlds)
.setParameter("endOfTime", END_OF_TIME)
.getSingleResult();
return Math.max(activeDomains.intValue() / 1000, 1);
return Duration.standardSeconds(Math.max(activeDomains / refreshQps, 1));
}
private ImmutableList<String> getBatch(Optional<String> lastInPreviousBatch) {
@@ -127,11 +140,12 @@ public class RefreshDnsForAllDomainsAction implements Runnable {
}
@VisibleForTesting
ImmutableList<String> refreshBatch(Optional<String> lastInPreviousBatch, int smearMinutes) {
ImmutableList<String> refreshBatch(Optional<String> lastInPreviousBatch, Duration smear) {
ImmutableList<String> domainBatch = getBatch(lastInPreviousBatch);
try {
// Smear the task execution time over the next N minutes.
requestDomainDnsRefresh(domainBatch, Duration.standardMinutes(random.nextInt(smearMinutes)));
// Smear the task execution time over the next N seconds.
requestDomainDnsRefresh(
domainBatch, Duration.standardSeconds(random.nextInt((int) smear.getStandardSeconds())));
} catch (Throwable t) {
logger.atSevere().withCause(t).log("Error while enqueuing DNS refresh batch");
response.setStatus(HttpStatus.SC_OK);

View File

@@ -81,4 +81,10 @@ public class ToolsServerModule {
static Optional<Integer> provideBatchSize(HttpServletRequest req) {
return extractOptionalIntParameter(req, "batchSize");
}
@Provides
@Parameter("refreshQps")
static Optional<Integer> provideRefreshQps(HttpServletRequest req) {
return extractOptionalIntParameter(req, "refreshQps");
}
}

View File

@@ -0,0 +1,72 @@
// Copyright 2023 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.
package google.registry.ui.server.console;
import static google.registry.request.Action.Method.GET;
import com.google.api.client.http.HttpStatusCodes;
import google.registry.model.console.User;
import google.registry.security.XsrfTokenManager;
import google.registry.ui.server.registrar.ConsoleApiParams;
import java.util.Arrays;
import java.util.Optional;
import javax.servlet.http.Cookie;
/** Base class for handling Console API requests */
public abstract class ConsoleApiAction implements Runnable {
protected ConsoleApiParams consoleApiParams;
public ConsoleApiAction(ConsoleApiParams consoleApiParams) {
this.consoleApiParams = consoleApiParams;
}
@Override
public final void run() {
// Shouldn't be even possible because of Auth annotations on the various implementing classes
if (!consoleApiParams.authResult().userAuthInfo().get().consoleUser().isPresent()) {
consoleApiParams.response().setStatus(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
return;
}
User user = consoleApiParams.authResult().userAuthInfo().get().consoleUser().get();
if (consoleApiParams.request().getMethod().equals(GET.toString())) {
getHandler(user);
} else {
if (verifyXSRF()) {
postHandler(user);
}
}
}
protected void postHandler(User user) {
throw new UnsupportedOperationException("Console API POST handler not implemented");
}
protected void getHandler(User user) {
throw new UnsupportedOperationException("Console API GET handler not implemented");
}
private boolean verifyXSRF() {
Optional<Cookie> maybeCookie =
Arrays.stream(consoleApiParams.request().getCookies())
.filter(c -> XsrfTokenManager.X_CSRF_TOKEN.equals(c.getName()))
.findFirst();
if (!maybeCookie.isPresent()
|| !consoleApiParams.xsrfTokenManager().validateToken(maybeCookie.get().getValue())) {
consoleApiParams.response().setStatus(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
return false;
}
return true;
}
}

View File

@@ -21,12 +21,10 @@ import com.google.common.collect.ImmutableMap;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.console.User;
import google.registry.request.Action;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.UserAuthInfo;
import google.registry.ui.server.registrar.JsonGetAction;
import google.registry.ui.server.registrar.ConsoleApiParams;
import javax.inject.Inject;
import javax.servlet.http.Cookie;
import org.json.JSONObject;
@Action(
@@ -34,12 +32,10 @@ import org.json.JSONObject;
path = ConsoleUserDataAction.PATH,
method = {GET},
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public class ConsoleUserDataAction implements JsonGetAction {
public class ConsoleUserDataAction extends ConsoleApiAction {
public static final String PATH = "/console-api/userdata";
private final AuthResult authResult;
private final Response response;
private final String productName;
private final String supportPhoneNumber;
private final String supportEmail;
@@ -47,14 +43,12 @@ public class ConsoleUserDataAction implements JsonGetAction {
@Inject
public ConsoleUserDataAction(
AuthResult authResult,
Response response,
ConsoleApiParams consoleApiParams,
@Config("productName") String productName,
@Config("supportEmail") String supportEmail,
@Config("supportPhoneNumber") String supportPhoneNumber,
@Config("technicalDocsUrl") String technicalDocsUrl) {
this.response = response;
this.authResult = authResult;
super(consoleApiParams);
this.productName = productName;
this.supportEmail = supportEmail;
this.supportPhoneNumber = supportPhoneNumber;
@@ -62,13 +56,15 @@ public class ConsoleUserDataAction implements JsonGetAction {
}
@Override
public void run() {
UserAuthInfo authInfo = authResult.userAuthInfo().get();
if (!authInfo.consoleUser().isPresent()) {
response.setStatus(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
return;
}
User user = authInfo.consoleUser().get();
protected void getHandler(User user) {
// As this is a first GET request we use it as an opportunity to set a XSRF cookie
// for angular to read - https://angular.io/guide/http-security-xsrf-protection
Cookie xsrfCookie =
new Cookie(
consoleApiParams.xsrfTokenManager().X_CSRF_TOKEN,
consoleApiParams.xsrfTokenManager().generateToken(user.getEmailAddress()));
xsrfCookie.setSecure(true);
consoleApiParams.response().addCookie(xsrfCookie);
JSONObject json =
new JSONObject(
@@ -90,7 +86,7 @@ public class ConsoleUserDataAction implements JsonGetAction {
// Is used by UI to construct a link to registry resources
"technicalDocsUrl", technicalDocsUrl));
response.setPayload(json.toString());
response.setStatus(HttpStatusCodes.STATUS_CODE_OK);
consoleApiParams.response().setPayload(json.toString());
consoleApiParams.response().setStatus(HttpStatusCodes.STATUS_CODE_OK);
}
}

View File

@@ -0,0 +1,41 @@
// Copyright 2023 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.
package google.registry.ui.server.registrar;
import com.google.auto.value.AutoValue;
import google.registry.request.Response;
import google.registry.request.auth.AuthResult;
import google.registry.security.XsrfTokenManager;
import javax.servlet.http.HttpServletRequest;
/** Groups necessary dependencies for Console API actions * */
@AutoValue
public abstract class ConsoleApiParams {
public static ConsoleApiParams create(
HttpServletRequest request,
Response response,
AuthResult authResult,
XsrfTokenManager xsrfTokenManager) {
return new AutoValue_ConsoleApiParams(request, response, authResult, xsrfTokenManager);
}
public abstract HttpServletRequest request();
public abstract Response response();
public abstract AuthResult authResult();
public abstract XsrfTokenManager xsrfTokenManager();
}

View File

@@ -28,6 +28,10 @@ import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.request.OptionalJsonPayload;
import google.registry.request.Parameter;
import google.registry.request.RequestScope;
import google.registry.request.Response;
import google.registry.request.auth.AuthResult;
import google.registry.security.XsrfTokenManager;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import org.joda.time.DateTime;
@@ -35,9 +39,18 @@ import org.joda.time.DateTime;
/** Dagger module for the Registrar Console parameters. */
@Module
public final class RegistrarConsoleModule {
static final String PARAM_CLIENT_ID = "clientId";
@Provides
@RequestScope
ConsoleApiParams provideConsoleApiParams(
HttpServletRequest request,
Response response,
AuthResult authResult,
XsrfTokenManager xsrfTokenManager) {
return ConsoleApiParams.create(request, response, authResult, xsrfTokenManager);
}
@Provides
@Parameter(PARAM_CLIENT_ID)
static Optional<String> provideOptionalClientId(HttpServletRequest req) {

View File

@@ -38,6 +38,9 @@
<mapping-file>META-INF/orm.xml</mapping-file>
<class>google.registry.bsa.persistence.BsaDownload</class>
<class>google.registry.bsa.persistence.BsaLabel</class>
<class>google.registry.bsa.persistence.BsaDomainInUse</class>
<class>google.registry.model.billing.BillingCancellation</class>
<class>google.registry.model.billing.BillingEvent</class>
<class>google.registry.model.billing.BillingRecurrence</class>

View File

@@ -0,0 +1,94 @@
// Copyright 2023 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.
package google.registry.bsa;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.tldconfig.idn.IdnTableEnum.EXTENDED_LATIN;
import static google.registry.tldconfig.idn.IdnTableEnum.JA;
import static google.registry.tldconfig.idn.IdnTableEnum.UNCONFUSABLE_LATIN;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import google.registry.model.tld.Tld;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class IdnCheckerTest {
@Mock Tld jaonly;
@Mock Tld jandelatin;
@Mock Tld strictlatin;
IdnChecker idnChecker;
@BeforeEach
void setup() {
idnChecker =
new IdnChecker(
ImmutableMap.of(
JA,
ImmutableSet.of(jandelatin, jaonly),
EXTENDED_LATIN,
ImmutableSet.of(jandelatin),
UNCONFUSABLE_LATIN,
ImmutableSet.of(strictlatin)));
}
@Test
void getAllValidIdns_allTlds() {
assertThat(idnChecker.getAllValidIdns("all"))
.containsExactly(EXTENDED_LATIN, JA, UNCONFUSABLE_LATIN);
}
@Test
void getAllValidIdns_notJa() {
assertThat(idnChecker.getAllValidIdns("à")).containsExactly(EXTENDED_LATIN, UNCONFUSABLE_LATIN);
}
@Test
void getAllValidIdns_extendedLatinOnly() {
assertThat(idnChecker.getAllValidIdns("á")).containsExactly(EXTENDED_LATIN);
}
@Test
void getAllValidIdns_jaOnly() {
assertThat(idnChecker.getAllValidIdns("")).containsExactly(JA);
}
@Test
void getAllValidIdns_none() {
assertThat(idnChecker.getAllValidIdns("д")).isEmpty();
}
@Test
void getSupportingTlds_singleTld_success() {
assertThat(idnChecker.getSupportingTlds(ImmutableSet.of("EXTENDED_LATIN")))
.containsExactly(jandelatin);
}
@Test
void getSupportingTlds_multiTld_success() {
assertThat(idnChecker.getSupportingTlds(ImmutableSet.of("JA")))
.containsExactly(jandelatin, jaonly);
}
@Test
void getForbiddingTlds_success() {
assertThat(idnChecker.getForbiddingTlds(ImmutableSet.of("JA"))).containsExactly(strictlatin);
}
}

View File

@@ -0,0 +1,74 @@
// Copyright 2023 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.
package google.registry.bsa.persistence;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static org.joda.time.DateTimeZone.UTC;
import static org.junit.jupiter.api.Assertions.assertThrows;
import google.registry.bsa.persistence.BsaDomainInUse.Reason;
import google.registry.persistence.transaction.DatabaseException;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationWithCoverageExtension;
import google.registry.testing.FakeClock;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link BsaDomainInUse}. */
public class BsaDomainInUseTest {
protected FakeClock fakeClock = new FakeClock(DateTime.now(UTC));
@RegisterExtension
final JpaIntegrationWithCoverageExtension jpa =
new JpaTestExtensions.Builder().withClock(fakeClock).buildIntegrationWithCoverageExtension();
@Test
void persist() {
tm().transact(() -> tm().put(new BsaLabel("label", fakeClock.nowUtc())));
tm().transact(() -> tm().put(new BsaDomainInUse("label", "tld", Reason.REGISTERED)));
BsaDomainInUse persisted =
tm().transact(() -> tm().loadByKey(BsaDomainInUse.vKey("label", "tld")));
assertThat(persisted.label).isEqualTo("label");
assertThat(persisted.tld).isEqualTo("tld");
assertThat(persisted.reason).isEqualTo(Reason.REGISTERED);
}
@Test
void cascadeDeletion() {
tm().transact(() -> tm().put(new BsaLabel("label", fakeClock.nowUtc())));
tm().transact(() -> tm().put(new BsaDomainInUse("label", "tld", Reason.REGISTERED)));
assertThat(tm().transact(() -> tm().loadByKeyIfPresent(BsaDomainInUse.vKey("label", "tld"))))
.isPresent();
tm().transact(() -> tm().delete(BsaLabel.vKey("label")));
assertThat(tm().transact(() -> tm().loadByKeyIfPresent(BsaDomainInUse.vKey("label", "tld"))))
.isEmpty();
}
@Test
void insertDomainWithoutLabel() {
assertThat(
assertThrows(
DatabaseException.class,
() ->
tm().transact(
() -> tm().put(new BsaDomainInUse("label", "tld", Reason.REGISTERED)))))
.hasMessageThat()
.contains("violates foreign key constraint");
}
}

View File

@@ -0,0 +1,66 @@
// Copyright 2023 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.
package google.registry.bsa.persistence;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.bsa.BlockList.BLOCK;
import static google.registry.bsa.BlockList.BLOCK_PLUS;
import static google.registry.bsa.DownloadStage.DOWNLOAD;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static org.joda.time.DateTimeZone.UTC;
import com.google.common.collect.ImmutableMap;
import google.registry.bsa.BlockList;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationWithCoverageExtension;
import google.registry.testing.FakeClock;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit test for {@link BsaDownload}. */
public class BsaDownloadTest {
protected FakeClock fakeClock = new FakeClock(DateTime.now(UTC));
@RegisterExtension
final JpaIntegrationWithCoverageExtension jpa =
new JpaTestExtensions.Builder().withClock(fakeClock).buildIntegrationWithCoverageExtension();
@Test
void saveJob() {
BsaDownload persisted = tm().transact(() -> tm().getEntityManager().merge(new BsaDownload()));
assertThat(persisted.jobId).isNotNull();
assertThat(persisted.creationTime.getTimestamp()).isEqualTo(fakeClock.nowUtc());
assertThat(persisted.stage).isEqualTo(DOWNLOAD);
}
@Test
void loadJobByKey() {
BsaDownload persisted = tm().transact(() -> tm().getEntityManager().merge(new BsaDownload()));
assertThat(tm().transact(() -> tm().loadByKey(BsaDownload.vKey(persisted.jobId))))
.isEqualTo(persisted);
}
@Test
void checksums() {
BsaDownload job = new BsaDownload();
assertThat(job.getChecksums()).isEmpty();
ImmutableMap<BlockList, String> checksums = ImmutableMap.of(BLOCK, "a", BLOCK_PLUS, "b");
job.setBlockListChecksums(checksums);
assertThat(job.getChecksums()).isEqualTo(checksums);
assertThat(job.blockListChecksums).isEqualTo("BLOCK=a,BLOCK_PLUS=b");
}
}

View File

@@ -0,0 +1,44 @@
// Copyright 2023 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.
package google.registry.bsa.persistence;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static org.joda.time.DateTimeZone.UTC;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationWithCoverageExtension;
import google.registry.testing.FakeClock;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link BsaLabel}. */
public class BsaLabelTest {
protected FakeClock fakeClock = new FakeClock(DateTime.now(UTC));
@RegisterExtension
final JpaIntegrationWithCoverageExtension jpa =
new JpaTestExtensions.Builder().withClock(fakeClock).buildIntegrationWithCoverageExtension();
@Test
void persist() {
tm().transact(() -> tm().put(new BsaLabel("label", fakeClock.nowUtc())));
BsaLabel persisted = tm().transact(() -> tm().loadByKey(BsaLabel.vKey("label")));
assertThat(persisted.getLabel()).isEqualTo("label");
assertThat(persisted.creationTime).isEqualTo(fakeClock.nowUtc());
}
}

View File

@@ -138,6 +138,7 @@ public class GmailClientTest {
assertThat(attachment.getContentType()).startsWith(CSV_UTF_8.toString());
assertThat(attachment.getContentType()).endsWith("name=filename");
assertThat(attachment.getContent()).isEqualTo("foo,bar\nbaz,qux");
assertThat(attachment.getDisposition()).isEqualTo("attachment");
}
@Test

View File

@@ -0,0 +1,36 @@
// Copyright 2023 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.
package google.registry.module.bsa;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.Test;
class BsaServletTest {
private final HttpServletRequest req = mock(HttpServletRequest.class);
private final HttpServletResponse rsp = mock(HttpServletResponse.class);
@Test
void testService_unknownPath_returnsNotFound() throws Exception {
when(req.getMethod()).thenReturn("GET");
when(req.getRequestURI()).thenReturn("/lol");
new BsaServlet().service(req, rsp);
verify(rsp).sendError(404);
}
}

View File

@@ -19,7 +19,6 @@ import static com.google.common.truth.Truth.assertThat;
import static google.registry.request.auth.AuthModule.BEARER_PREFIX;
import static google.registry.request.auth.AuthModule.IAP_HEADER_NAME;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -70,7 +69,7 @@ public class OidcTokenAuthenticationMechanismTest {
private AuthResult authResult;
private OidcTokenAuthenticationMechanism authenticationMechanism =
new OidcTokenAuthenticationMechanism(serviceAccounts, tokenVerifier, e -> rawToken) {};
new OidcTokenAuthenticationMechanism(serviceAccounts, tokenVerifier, null, e -> rawToken) {};
@RegisterExtension
public final JpaTestExtensions.JpaUnitTestExtension jpaExtension =
@@ -78,7 +77,7 @@ public class OidcTokenAuthenticationMechanismTest {
@BeforeEach
void beforeEach() throws Exception {
when(tokenVerifier.verify(eq(rawToken))).thenReturn(jwt);
when(tokenVerifier.verify(rawToken)).thenReturn(jwt);
payload.setEmail(email);
payload.setSubject(gaiaId);
insertInDb(user);
@@ -98,18 +97,30 @@ public class OidcTokenAuthenticationMechanismTest {
@Test
void testAuthenticate_noTokenFromRequest() {
authenticationMechanism =
new OidcTokenAuthenticationMechanism(serviceAccounts, tokenVerifier, e -> null) {};
new OidcTokenAuthenticationMechanism(serviceAccounts, tokenVerifier, null, e -> null) {};
authResult = authenticationMechanism.authenticate(request);
assertThat(authResult).isEqualTo(AuthResult.NOT_AUTHENTICATED);
}
@Test
void testAuthenticate_invalidToken() throws Exception {
when(tokenVerifier.verify(eq(rawToken))).thenThrow(new VerificationException("Bad token"));
when(tokenVerifier.verify(rawToken)).thenThrow(new VerificationException("Bad token"));
authResult = authenticationMechanism.authenticate(request);
assertThat(authResult).isEqualTo(AuthResult.NOT_AUTHENTICATED);
}
@Test
void testAuthenticate_fallbackVerifier() throws Exception {
TokenVerifier fallbackVerifier = mock(TokenVerifier.class);
when(tokenVerifier.verify(rawToken)).thenThrow(new VerificationException("Bad token"));
when(fallbackVerifier.verify(rawToken)).thenReturn(jwt);
authenticationMechanism =
new OidcTokenAuthenticationMechanism(
serviceAccounts, tokenVerifier, fallbackVerifier, e -> rawToken) {};
authResult = authenticationMechanism.authenticate(request);
assertThat(authResult.isAuthenticated()).isEqualTo(true);
}
@Test
void testAuthenticate_noEmailAddress() throws Exception {
payload.setEmail(null);
@@ -223,5 +234,12 @@ public class OidcTokenAuthenticationMechanismTest {
String provideOauthClientId() {
return "client-id";
}
@Provides
@Singleton
@Config("fallbackOauthClientId")
String provideFallbackOauthClientId() {
return "fallback-client-id";
}
}
}

View File

@@ -16,6 +16,9 @@ package google.registry.schema.integration;
import static com.google.common.truth.Truth.assert_;
import google.registry.bsa.persistence.BsaDomainInUseTest;
import google.registry.bsa.persistence.BsaDownloadTest;
import google.registry.bsa.persistence.BsaLabelTest;
import google.registry.model.billing.BillingBaseTest;
import google.registry.model.common.CursorTest;
import google.registry.model.common.DnsRefreshRequestTest;
@@ -82,6 +85,9 @@ import org.junit.runner.RunWith;
BeforeSuiteTest.class,
AllocationTokenTest.class,
BillingBaseTest.class,
BsaDomainInUseTest.class,
BsaDownloadTest.class,
BsaLabelTest.class,
BulkPricingPackageTest.class,
ClaimsListDaoTest.class,
ContactHistoryTest.class,

View File

@@ -0,0 +1,46 @@
// Copyright 2023 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.
package google.registry.testing;
import static org.mockito.Mockito.mock;
import com.google.appengine.api.users.UserService;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.UserAuthInfo;
import google.registry.security.XsrfTokenManager;
import google.registry.ui.server.registrar.ConsoleApiParams;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import org.joda.time.DateTime;
public final class FakeConsoleApiParams {
public static ConsoleApiParams get(Optional<AuthResult> maybeAuthResult) {
AuthResult authResult =
maybeAuthResult.orElseGet(
() ->
AuthResult.createUser(
UserAuthInfo.create(
new com.google.appengine.api.users.User(
"JohnDoe@theregistrar.com", "theregistrar.com"),
false)));
return ConsoleApiParams.create(
mock(HttpServletRequest.class),
new FakeResponse(),
authResult,
new XsrfTokenManager(
new FakeClock(DateTime.parse("2020-02-02T01:23:45Z")), mock(UserService.class)));
}
}

View File

@@ -22,8 +22,10 @@ import static java.util.Collections.unmodifiableMap;
import com.google.common.base.Throwables;
import com.google.common.net.MediaType;
import google.registry.request.Response;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.Cookie;
import org.joda.time.DateTime;
/** Fake implementation of {@link Response} for testing. */
@@ -36,6 +38,8 @@ public final class FakeResponse implements Response {
private boolean wasMutuallyExclusiveResponseSet;
private String lastResponseStackTrace;
private ArrayList<Cookie> cookies = new ArrayList<>();
public int getStatus() {
return status;
}
@@ -83,6 +87,15 @@ public final class FakeResponse implements Response {
headers.put(checkNotNull(header), checkNotNull(timestamp));
}
@Override
public void addCookie(Cookie cookie) {
cookies.add(cookie);
}
public ArrayList<Cookie> getCookies() {
return cookies;
}
private void checkResponsePerformedOnce() {
checkState(
!wasMutuallyExclusiveResponseSet,

View File

@@ -14,11 +14,14 @@
package google.registry.tldconfig.idn;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.persistResource;
import com.google.common.collect.ImmutableSet;
import google.registry.model.tld.Tld;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import org.junit.jupiter.api.Test;
@@ -118,4 +121,24 @@ class IdnLabelValidatorTest {
// Extended Latin shouldn't include Japanese characters
assertThat(idnLabelValidator.findValidIdnTableForTld("みんな", "tld")).isEmpty();
}
@Test
void testGetIdnTablesForTld_custom() {
persistResource(
createTld("tld")
.asBuilder()
.setIdnTables(ImmutableSet.of(IdnTableEnum.EXTENDED_LATIN))
.build());
Tld tld = tm().transact(() -> tm().loadByKey(Tld.createVKey("tld")));
assertThat(idnLabelValidator.getIdnTablesForTld(tld))
.containsExactly(IdnTableEnum.EXTENDED_LATIN);
}
@Test
void testGetIdnTablesForTld_default() {
persistResource(createTld("tld").asBuilder().build());
Tld tld = tm().transact(() -> tm().loadByKey(Tld.createVKey("tld")));
assertThat(idnLabelValidator.getIdnTablesForTld(tld))
.containsExactly(IdnTableEnum.EXTENDED_LATIN, IdnTableEnum.JA);
}
}

View File

@@ -16,6 +16,7 @@ package google.registry.tools;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.EntityYamlUtils.createObjectMapper;
import static google.registry.model.domain.token.AllocationToken.TokenType.DEFAULT_PROMO;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.persistPremiumList;
import static google.registry.testing.DatabaseHelper.persistResource;
@@ -47,6 +48,8 @@ import google.registry.model.tld.label.PremiumListDao;
import java.io.File;
import java.util.logging.Logger;
import org.joda.money.Money;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -98,6 +101,20 @@ public class ConfigureTldCommandTest extends CommandTestCase<ConfigureTldCommand
assertThat(updatedTld.getCreateBillingCost()).isEqualTo(Money.of(USD, 25));
testTldConfiguredSuccessfully(updatedTld, "tld.yaml");
assertThat(updatedTld.getBreakglassMode()).isFalse();
assertThat(tld.getBsaEnrollStartTime()).isNull();
}
@Test
void testSuccess_updateTld_bsaTimeUnaffected() throws Exception {
Tld tld = createTld("tld");
DateTime bsaStartTime = DateTime.now(DateTimeZone.UTC);
tm().transact(() -> tm().put(tld.asBuilder().setBsaEnrollStartTime(bsaStartTime).build()));
File tldFile = tmpDir.resolve("tld.yaml").toFile();
Files.asCharSink(tldFile, UTF_8).write(loadFile(getClass(), "tld.yaml"));
runCommandForced("--input=" + tldFile);
// TODO(11/30/2023): uncomment below two lines
// Tld updatedTld = Tld.get("tld");
// assertThat(tld.getBsaEnrollStartTime()).isEqualTo(bsaStartTime);
}
@Test

View File

@@ -35,6 +35,7 @@ import google.registry.testing.FakeResponse;
import java.util.Optional;
import java.util.Random;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@@ -55,7 +56,7 @@ public class RefreshDnsForAllDomainsActionTest {
createTld("bar");
action =
new RefreshDnsForAllDomainsAction(
response, ImmutableSet.of("bar"), Optional.of(10), new Random());
response, ImmutableSet.of("bar"), Optional.of(10), Optional.empty(), new Random());
}
@Test
@@ -74,9 +75,9 @@ public class RefreshDnsForAllDomainsActionTest {
// Set batch size to 1 since each batch will be enqueud at the same time
action =
new RefreshDnsForAllDomainsAction(
response, ImmutableSet.of("bar"), Optional.of(1), new Random());
tm().transact(() -> action.refreshBatch(Optional.empty(), 1000));
tm().transact(() -> action.refreshBatch(Optional.empty(), 1000));
response, ImmutableSet.of("bar"), Optional.of(1), Optional.of(7), new Random());
tm().transact(() -> action.refreshBatch(Optional.empty(), Duration.standardMinutes(1000)));
tm().transact(() -> action.refreshBatch(Optional.empty(), Duration.standardMinutes(1000)));
ImmutableList<DnsRefreshRequest> refreshRequests =
tm().transact(
() ->

View File

@@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assertWithMessage;
import com.google.common.collect.ImmutableSet;
import google.registry.request.Action;
import google.registry.request.JsonActionRunner;
import google.registry.ui.server.console.ConsoleApiAction;
import google.registry.ui.server.registrar.HtmlAction;
import google.registry.ui.server.registrar.JsonGetAction;
import io.github.classgraph.ClassGraph;
@@ -34,6 +35,7 @@ final class ActionMembershipTest {
// 1. Extending HtmlAction to signal that we are serving an HTML page
// 2. Extending JsonAction to show that we are serving JSON POST requests
// 3. Extending JsonGetAction to serve JSON GET requests
// 4. Extending ConsoleApiAction to serve JSON requests
ImmutableSet.Builder<String> failingClasses = new ImmutableSet.Builder<>();
try (ScanResult scanResult =
new ClassGraph().enableAnnotationInfo().whitelistPackages("google.registry.ui").scan()) {
@@ -41,7 +43,8 @@ final class ActionMembershipTest {
.getClassesWithAnnotation(Action.class.getName())
.forEach(
classInfo -> {
if (!classInfo.extendsSuperclass(HtmlAction.class.getName())
if (!classInfo.extendsSuperclass(ConsoleApiAction.class.getName())
&& !classInfo.extendsSuperclass(HtmlAction.class.getName())
&& !classInfo.implementsInterface(JsonActionRunner.JsonAction.class.getName())
&& !classInfo.implementsInterface(JsonGetAction.class.getName())) {
failingClasses.add(classInfo.getName());

View File

@@ -14,7 +14,9 @@
package google.registry.ui.server.console;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
import com.google.api.client.http.HttpStatusCodes;
import com.google.gson.Gson;
@@ -22,12 +24,18 @@ import google.registry.model.console.GlobalRole;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.request.Action;
import google.registry.request.RequestModule;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.UserAuthInfo;
import google.registry.testing.FakeConsoleApiParams;
import google.registry.testing.FakeResponse;
import google.registry.ui.server.registrar.ConsoleApiParams;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;
import java.util.Optional;
import javax.servlet.http.Cookie;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@@ -35,12 +43,31 @@ import org.junit.jupiter.api.extension.RegisterExtension;
class ConsoleUserDataActionTest {
private static final Gson GSON = RequestModule.provideGson();
private FakeResponse response = new FakeResponse();
private ConsoleApiParams consoleApiParams;
@RegisterExtension
final JpaTestExtensions.JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
@Test
void testSuccess_hasXSRFCookie() throws IOException {
User user =
new User.Builder()
.setEmailAddress("email@email.com")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
.build();
AuthResult authResult = AuthResult.createUser(UserAuthInfo.create(user));
ConsoleUserDataAction action =
createAction(
Optional.of(FakeConsoleApiParams.get(Optional.of(authResult))), Action.Method.GET);
action.run();
ArrayList<Cookie> cookies = ((FakeResponse) consoleApiParams.response()).getCookies();
assertThat(cookies.stream().map(cookie -> cookie.getName()).collect(toImmutableList()))
.containsExactly("X-CSRF-Token");
}
@Test
void testSuccess_getContactInfo() throws IOException {
User user =
@@ -49,10 +76,15 @@ class ConsoleUserDataActionTest {
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
.build();
ConsoleUserDataAction action = createAction(AuthResult.createUser(UserAuthInfo.create(user)));
AuthResult authResult = AuthResult.createUser(UserAuthInfo.create(user));
ConsoleUserDataAction action =
createAction(
Optional.of(FakeConsoleApiParams.get(Optional.of(authResult))), Action.Method.GET);
action.run();
assertThat(response.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_OK);
Map jsonObject = GSON.fromJson(response.getPayload(), Map.class);
assertThat(((FakeResponse) consoleApiParams.response()).getStatus())
.isEqualTo(HttpStatusCodes.STATUS_CODE_OK);
Map jsonObject =
GSON.fromJson(((FakeResponse) consoleApiParams.response()).getPayload(), Map.class);
assertThat(jsonObject)
.containsExactly(
"isAdmin",
@@ -71,19 +103,18 @@ class ConsoleUserDataActionTest {
@Test
void testFailure_notAConsoleUser() throws IOException {
ConsoleUserDataAction action =
createAction(
AuthResult.createUser(
UserAuthInfo.create(
new com.google.appengine.api.users.User(
"JohnDoe@theregistrar.com", "theregistrar.com"),
false)));
ConsoleUserDataAction action = createAction(Optional.empty(), Action.Method.GET);
action.run();
assertThat(response.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
assertThat(((FakeResponse) consoleApiParams.response()).getStatus())
.isEqualTo(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
}
private ConsoleUserDataAction createAction(AuthResult authResult) throws IOException {
private ConsoleUserDataAction createAction(
Optional<ConsoleApiParams> maybeConsoleApiParams, Action.Method method) throws IOException {
consoleApiParams =
maybeConsoleApiParams.orElseGet(() -> FakeConsoleApiParams.get(Optional.empty()));
when(consoleApiParams.request().getMethod()).thenReturn(method.toString());
return new ConsoleUserDataAction(
authResult, response, "Nomulus", "support@example.com", "+1 (212) 867 5309", "test");
consoleApiParams, "Nomulus", "support@example.com", "+1 (212) 867 5309", "test");
}
}

View File

@@ -261,11 +261,11 @@ td.section {
</tr>
<tr>
<td class="property_name">generated on</td>
<td class="property_value">2023-11-02 18:26:18.901466</td>
<td class="property_value">2023-11-09 01:49:59.861801</td>
</tr>
<tr>
<td class="property_name">last flyway file</td>
<td id="lastFlywayFile" class="property_value">V149__add_bsa_domain_in_use_table.sql</td>
<td id="lastFlywayFile" class="property_value">V150__add_tld_bsa_enroll_date.sql</td>
</tr>
</tbody>
</table>
@@ -285,7 +285,7 @@ td.section {
generated on
</text>
<text text-anchor="start" x="3835.5" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">
2023-11-02 18:26:18.901466
2023-11-09 01:49:59.861801
</text>
<polygon fill="none" stroke="#888888" points="3748,-4 3748,-44 4013,-44 4013,-4 3748,-4" /> <!-- allocationtoken_a08ccbef -->
<g id="node1" class="node">

File diff suppressed because it is too large Load Diff

View File

@@ -147,3 +147,4 @@ V146__last_update_time_via_epp.sql
V147__drop_gaia_id_from_user.sql
V148__add_bsa_download_and_label_tables.sql
V149__add_bsa_domain_in_use_table.sql
V150__add_tld_bsa_enroll_date.sql

View File

@@ -0,0 +1,15 @@
-- Copyright 2023 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.
ALTER TABLE public."Tld" ADD COLUMN bsa_enroll_start_time timestamptz;

View File

@@ -85,6 +85,29 @@
primary key (billing_recurrence_id)
);
create table "BsaDomainInUse" (
label text not null,
tld text not null,
creation_time timestamptz not null,
reason text not null,
primary key (label, tld)
);
create table "BsaDownload" (
job_id bigserial not null,
block_list_checksums text not null,
creation_time timestamptz not null,
stage text not null,
update_timestamp timestamptz,
primary key (job_id)
);
create table "BsaLabel" (
label text not null,
creation_time timestamptz not null,
primary key (label)
);
create table "ClaimsEntry" (
revision_id int8 not null,
domain_label text not null,
@@ -785,6 +808,7 @@ create index IDXoqttafcywwdn41um6kwlt0n8b on "BillingRecurrence" (domain_repo_id
create index IDXp3usbtvk0v1m14i5tdp4xnxgc on "BillingRecurrence" (recurrence_end_time);
create index IDXp0pxi708hlu4n40qhbtihge8x on "BillingRecurrence" (recurrence_last_expansion);
create index IDXjny8wuot75b5e6p38r47wdawu on "BillingRecurrence" (recurrence_time_of_year);
create index IDXj874kw19bgdnkxo1rue45jwlw on "BsaDownload" (creation_time);
create index IDX3y752kr9uh4kh6uig54vemx0l on "Contact" (creation_time);
create index IDXtm415d6fe1rr35stm33s5mg18 on "Contact" (current_sponsor_registrar_id);
create index IDXn1f711wicdnooa2mqb7g1m55o on "Contact" (deletion_time);

View File

@@ -1146,7 +1146,8 @@ CREATE TABLE public."Tld" (
dns_ds_ttl interval,
dns_ns_ttl interval,
idn_tables text[],
breakglass_mode boolean DEFAULT false NOT NULL
breakglass_mode boolean DEFAULT false NOT NULL,
bsa_enroll_start_time timestamp with time zone
);

View File

@@ -66,6 +66,14 @@ sized to support not just the normal ongoing DNS load but also the load incurred
by MapReduces, both scheduled (such as RDE) and on-demand (asynchronous
contact/host deletion).
#### BSA service
The bsa service is responsible for business logic behind Nomulus and BSA
functionality. Requests to the backend service are handled by the `BsaServlet`,
which provides all of the endpoints exposed in `BsaRequestComponent`. These
include tasks for downloading, processing and uploading BSA data.
#### Tools service
The tools service is responsible for servicing requests from the `nomulus`

View File

@@ -46,7 +46,7 @@ else
-PmavenUrl="${gcs_prefix}"/maven \
-PpluginsUrl="${gcs_prefix}"/plugins
for service in default pubapi backend tools
for service in default pubapi backend bsa tools
do
mv services/"${service}"/build/staged-app "${dest}/${service}"
done

View File

@@ -43,7 +43,7 @@ steps:
gcloud auth activate-service-account --key-file=tool-credential.json
for service in default pubapi backend tools
for service in default pubapi backend bsa tools
do
for version in $(gcloud app versions list \
--filter="SERVICE:$service AND SERVING_STATUS:STOPPED" \

View File

@@ -75,7 +75,7 @@ steps:
gcloud app versions list \
--project $project_id --hide-no-traffic \
--format="csv[no-heading](SERVICE,VERSION.ID)" | \
grep -e "^backend\|^default\|^pubapi\|^tools" |\
grep -e "^backend\|^default\|^bsa\|^pubapi\|^tools" |\
while read line; do echo "${TAG_NAME},$line"; done | tee "$local_map"
num_versions=$(cat "$local_map" | wc -l)
if [ "$num_versions" -ne 4 ]; then

View File

@@ -30,8 +30,7 @@ FROM gcr.io/${PROJECT_ID}/builder:${TAG_NAME}
COPY deploy_sql_schema.sh /usr/local/bin/
RUN \
FLYWAY_MAVEN=https://repo1.maven.org/maven2/org/flywaydb/flyway-commandline \
&& FLYWAY_VERSION=$(curl ${FLYWAY_MAVEN}/maven-metadata.xml \
| grep -oP "<release>\K.*(?=</release>)") \
&& FLYWAY_VERSION="9.22.3" \
&& echo "Downloading Flyway-commandline-${FLYWAY_VERSION}" \
&& mkdir -p /flyway \
&& curl -L ${FLYWAY_MAVEN}/${FLYWAY_VERSION}/flyway-commandline-${FLYWAY_VERSION}.tar.gz \

View File

@@ -43,6 +43,7 @@ include 'proxy'
include 'util'
include 'services:default'
include 'services:backend'
include 'services:bsa'
include 'services:tools'
include 'services:pubapi'
include 'java8compatibility'