mirror of
https://github.com/google/nomulus
synced 2026-07-26 10:03:23 +00:00
Compare commits
102
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
302faa21dd | ||
|
|
b187193e79 | ||
|
|
b1c6e07fa2 | ||
|
|
718ed29070 | ||
|
|
a2f0035f89 | ||
|
|
7e0489e5f9 | ||
|
|
d29a98bdd3 | ||
|
|
d6dd95b052 | ||
|
|
6b74924067 | ||
|
|
71e9bc95a7 | ||
|
|
bf54a0d0c4 | ||
|
|
2a04b9be9b | ||
|
|
675354ae65 | ||
|
|
21421726e0 | ||
|
|
192a7e6c4e | ||
|
|
542084eb70 | ||
|
|
54e605fa27 | ||
|
|
188670e156 | ||
|
|
2fd609ed03 | ||
|
|
fc805ba4d9 | ||
|
|
21976bee24 | ||
|
|
c2bd11c528 | ||
|
|
653da19e53 | ||
|
|
4df734da2a | ||
|
|
b8a51cacc4 | ||
|
|
5067b3d339 | ||
|
|
93ec04018f | ||
|
|
ae310ea141 | ||
|
|
c785365590 | ||
|
|
6ad85a2d21 | ||
|
|
2e43295d63 | ||
|
|
a7118dfbba | ||
|
|
0bf0b4fc66 | ||
|
|
2ce91e3477 | ||
|
|
4d6b5a82df | ||
|
|
1fe1043306 | ||
|
|
b1e42cfd5e | ||
|
|
0fa82e30bb | ||
|
|
6608ee282d | ||
|
|
22c867f3f2 | ||
|
|
160abef731 | ||
|
|
a6e4017971 | ||
|
|
d9a857133a | ||
|
|
c7a27061d8 | ||
|
|
7766db36a7 | ||
|
|
eda0f7ad7c | ||
|
|
67527f1560 | ||
|
|
4aeba6e3f7 | ||
|
|
d6f1f5894b | ||
|
|
47ad569cb0 | ||
|
|
06934daf94 | ||
|
|
9a032e4bb9 | ||
|
|
0ab612ab23 | ||
|
|
403c7ad275 | ||
|
|
11d625b837 | ||
|
|
6a47287da7 | ||
|
|
cdc0ffe831 | ||
|
|
7c23413d83 | ||
|
|
fe222bbdcf | ||
|
|
f770f6a46d | ||
|
|
e071f5579c | ||
|
|
6080cd2f7a | ||
|
|
90f583910e | ||
|
|
a85bf5c30a | ||
|
|
dcfe939c38 | ||
|
|
ae61922318 | ||
|
|
84e97aa2db | ||
|
|
4df4bf1489 | ||
|
|
9faabb0f1d | ||
|
|
ea9d717378 | ||
|
|
d1769b29ef | ||
|
|
14376953e5 | ||
|
|
c13c9b53e3 | ||
|
|
1cd6026151 | ||
|
|
75524fd403 | ||
|
|
a5b280838c | ||
|
|
5599a0eb3d | ||
|
|
dde41078cd | ||
|
|
0030645b1a | ||
|
|
c5abd2a7c9 | ||
|
|
5286b1a0dc | ||
|
|
cd65a4c9d8 | ||
|
|
beb7fcc16e | ||
|
|
d68f3e5cc0 | ||
|
|
33d30ea6a1 | ||
|
|
00ee62f877 | ||
|
|
2bc07349a4 | ||
|
|
00522fb618 | ||
|
|
ad992beff9 | ||
|
|
ba91141505 | ||
|
|
c1ce73db49 | ||
|
|
00ceb6a7df | ||
|
|
c731b18304 | ||
|
|
5fd6e6cdaa | ||
|
|
53b92d602e | ||
|
|
b3fc57c7f7 | ||
|
|
bd70cd91a5 | ||
|
|
73725e94fe | ||
|
|
c3f8ec8c85 | ||
|
|
3894ca6971 | ||
|
|
5f06581572 | ||
|
|
17b851de42 |
@@ -0,0 +1,30 @@
|
||||
---
|
||||
name: java-ast-refactoring
|
||||
description: "AST-aware Java refactoring using OpenRewrite. Use when asked to structurally refactor Java code, change class names, change method signatures/overloads, replace builder patterns, modify annotations, or perform cross-file structural replacements. Note: Renaming fields or local variables/parameters is not supported natively via simple YAML recipes in the standard openrewrite modules."
|
||||
---
|
||||
|
||||
# AST-Aware Java Refactoring
|
||||
|
||||
This skill uses OpenRewrite to perform Abstract Syntax Tree (AST) based refactoring on Java codebases. This is highly preferred over text-based regex or python scripts because it understands Java semantics, correctly updates imports, and preserves formatting.
|
||||
|
||||
## Parameter and Field Renaming (Last Resort)
|
||||
|
||||
Because OpenRewrite's YAML recipes do not natively support simple variable or field renaming, a custom script is provided:
|
||||
```bash
|
||||
python3 .gemini/skills/java-ast-refactoring/scripts/safe_rename.py <filepath> <old_name> <new_name>
|
||||
```
|
||||
**CRITICAL:** Running this python script is a LAST RESORT. It is a regex-based token replacement that ignores strings and comments, but it lacks true AST understanding. ALWAYS prefer using OpenRewrite recipes (`rewrite.yml`) for structural changes like renaming classes, methods, or moving targets, as OpenRewrite correctly handles imports, types, and cross-file references safely.
|
||||
|
||||
## Usage
|
||||
|
||||
1. Create a `rewrite.yml` recipe file in the workspace root. Refer to `.gemini/skills/java-ast-refactoring/references/rewrite_recipes.md` for syntax.
|
||||
2. Execute the script:
|
||||
```bash
|
||||
./.gemini/skills/java-ast-refactoring/scripts/run_rewrite.sh rewrite.yml
|
||||
```
|
||||
3. The script will safely apply the AST transformations and then automatically run `./gradlew spotlessApply` and `./gradlew javaIncrementalFormatApply` on the modified files to automatically fix any Checkstyle line-length and import ordering issues caused by longer/shorter identifiers. Verify the output using `git diff`.
|
||||
4. **MANDATORY:** Always run `./gradlew build -x test` (or the equivalent compile task) after running OpenRewrite to ensure no compilation errors were introduced.
|
||||
|
||||
## Known Limitations & Troubleshooting
|
||||
* **Static Imports Dropped on Class Rename:** When using `ChangeType` to rename a class, OpenRewrite may sometimes drop static imports for fields/constants belonging to the old class instead of updating them to the new class. If compilation fails due to "cannot find symbol" for a constant after a class rename, manually restore the static import (e.g., `import static com.new.ClassName.CONSTANT;`).
|
||||
* **Continuous Improvement:** If any new issues or edge cases are found while running the refactoring (e.g., build failures, formatting issues, or missed transformations), you **MUST** proactively ask the user if you should permanently update this skill file (`SKILL.md`) and its accompanying scripts (`scripts/run_rewrite.sh`, `scripts/safe_rename.py`) to fix the issue for future use. Do not wait for the user to prompt you to fix the infrastructure.
|
||||
@@ -0,0 +1,82 @@
|
||||
# OpenRewrite Recipe Reference
|
||||
|
||||
OpenRewrite uses declarative YAML recipes to perform structural refactorings.
|
||||
|
||||
## Recipe Structure
|
||||
|
||||
A recipe file must have a `type`, a `name` (which you will activate), and a `recipeList` containing specific core recipes to execute sequentially.
|
||||
|
||||
```yaml
|
||||
type: specs.openrewrite.org/v1beta/recipe
|
||||
name: com.example.MyRefactoring
|
||||
recipeList:
|
||||
- <CoreRecipe>:
|
||||
<argument1>: <value>
|
||||
```
|
||||
|
||||
## Core Recipes for Common Operations
|
||||
|
||||
### 1. Change Method Name
|
||||
```yaml
|
||||
- org.openrewrite.java.ChangeMethodName:
|
||||
methodPattern: java.util.Collections emptyList()
|
||||
newMethodName: emptyList
|
||||
```
|
||||
|
||||
### 2. Change Method Target to Static
|
||||
Moves a method call to a new static method target. Useful for replacing custom utility methods with standard ones.
|
||||
```yaml
|
||||
- org.openrewrite.java.ChangeMethodTargetToStatic:
|
||||
methodPattern: google.registry.model.eppinput.EppInputs createDomain(java.lang.String, java.lang.String)
|
||||
fullyQualifiedTargetTypeName: google.registry.model.domain.DomainCommand.Create
|
||||
returnType: google.registry.model.domain.DomainCommand.Create.Builder
|
||||
```
|
||||
|
||||
### 3. Change Type (Rename/Move Class)
|
||||
Updates the class name and automatically updates all imports across the codebase.
|
||||
*Note: OpenRewrite occasionally drops `import static` references to fields inside the renamed class. Be prepared to manually restore them if a compilation error occurs.*
|
||||
```yaml
|
||||
- org.openrewrite.java.ChangeType:
|
||||
oldFullyQualifiedTypeName: org.joda.time.Instant
|
||||
newFullyQualifiedTypeName: java.time.Instant
|
||||
```
|
||||
|
||||
### 4. Remove Unused Imports
|
||||
```yaml
|
||||
- org.openrewrite.java.RemoveUnusedImports
|
||||
```
|
||||
|
||||
### 5. Change Annotation
|
||||
```yaml
|
||||
- org.openrewrite.java.ChangeAnnotation:
|
||||
annotationPattern: @org.junit.Ignore
|
||||
newAnnotation: @org.junit.jupiter.api.Disabled
|
||||
```
|
||||
|
||||
### 6. Remove Annotation
|
||||
```yaml
|
||||
- org.openrewrite.java.RemoveAnnotation:
|
||||
annotationPattern: @java.lang.SuppressWarnings("unchecked")
|
||||
```
|
||||
|
||||
### 7. Change Method Arguments
|
||||
Reorders or removes arguments based on a target signature. `newArgumentTemplate` uses 0-based indexing.
|
||||
```yaml
|
||||
- org.openrewrite.java.ChangeMethodAccessLevel:
|
||||
methodPattern: com.google.common.collect.ImmutableList of(..)
|
||||
newAccessLevel: protected
|
||||
```
|
||||
|
||||
### 8. Add Import
|
||||
```yaml
|
||||
- org.openrewrite.java.AddImport:
|
||||
type: java.util.List
|
||||
```
|
||||
|
||||
## Method Patterns
|
||||
OpenRewrite uses a specific pointcut expression language for `methodPattern`:
|
||||
* `[return-type] [fully-qualified-class-name] [method-name]([parameter-types])`
|
||||
* `*` matches any type.
|
||||
* `..` matches any number of parameters.
|
||||
* Example: `java.lang.String split(java.lang.String, int)`
|
||||
* Example: `* java.util.List add(..)`
|
||||
@@ -0,0 +1,80 @@
|
||||
#!/bin/bash
|
||||
# Copyright 2026 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.
|
||||
|
||||
# Wrapper script to dynamically execute OpenRewrite without modifying build.gradle
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 <path-to-rewrite.yml>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RECIPE_FILE=$(realpath "$1")
|
||||
if [ ! -f "$RECIPE_FILE" ]; then
|
||||
echo "Error: Recipe file $RECIPE_FILE not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract the name of the recipe from the YAML to activate it
|
||||
RECIPE_NAME=$(grep -oP '(?<=name: ).*' "$RECIPE_FILE" | head -n 1)
|
||||
|
||||
if [ -z "$RECIPE_NAME" ]; then
|
||||
echo "Error: Could not extract 'name:' from $RECIPE_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
INIT_SCRIPT="rewrite-init.gradle"
|
||||
|
||||
cat << EOF > "$INIT_SCRIPT"
|
||||
initscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
dependencies {
|
||||
classpath("org.openrewrite.rewrite:org.openrewrite.rewrite.gradle.plugin:7.33.0")
|
||||
}
|
||||
}
|
||||
|
||||
rootProject {
|
||||
apply plugin: org.openrewrite.gradle.RewritePlugin
|
||||
|
||||
rewrite {
|
||||
activeRecipe("$RECIPE_NAME")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
rewrite("org.openrewrite.recipe:rewrite-testing-frameworks:2.14.0")
|
||||
rewrite("org.openrewrite.recipe:rewrite-migrate-java:2.11.0")
|
||||
rewrite("org.openrewrite.recipe:rewrite-spring:5.7.0")
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
apply plugin: org.openrewrite.gradle.RewritePlugin
|
||||
}
|
||||
EOF
|
||||
|
||||
# Copy the recipe file to the workspace root temporarily so OpenRewrite finds it
|
||||
cp "$RECIPE_FILE" ./rewrite.yml
|
||||
|
||||
echo "Executing OpenRewrite recipe: $RECIPE_NAME"
|
||||
./gradlew --init-script "$INIT_SCRIPT" rewriteRun --no-parallel --no-configuration-cache
|
||||
|
||||
echo "Running code formatters to fix Checkstyle line-length and import ordering..."
|
||||
./gradlew spotlessApply javaIncrementalFormatApply
|
||||
|
||||
# Clean up temporary files
|
||||
rm "$INIT_SCRIPT"
|
||||
rm ./rewrite.yml
|
||||
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright 2026 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.
|
||||
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
|
||||
def usage():
|
||||
print("Usage: python safe_rename.py <filepath> <old_name> <new_name>")
|
||||
print("Safely renames an identifier in a Java file, ignoring strings and comments.")
|
||||
sys.exit(1)
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 4:
|
||||
usage()
|
||||
|
||||
filepath = sys.argv[1]
|
||||
old_name = sys.argv[2]
|
||||
new_name = sys.argv[3]
|
||||
|
||||
if not os.path.exists(filepath):
|
||||
print(f"Error: File {filepath} not found.")
|
||||
sys.exit(1)
|
||||
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Regex to tokenize Java source safely.
|
||||
token_pattern = re.compile(
|
||||
r'(?P<string>"(?:\\.|[^"\\])*")|'
|
||||
r'(?P<char>\'(?:\\.|[^\'\\])*\')|'
|
||||
r'(?P<line_comment>//.*)|'
|
||||
r'(?P<block_comment>/\*[\s\S]*?\*/)|'
|
||||
r'(?P<ident>[a-zA-Z_$][a-zA-Z0-9_$]*)'
|
||||
)
|
||||
|
||||
def replacer(match):
|
||||
if match.group('ident') == old_name:
|
||||
return new_name
|
||||
return match.group(0)
|
||||
|
||||
new_content = token_pattern.sub(replacer, content)
|
||||
|
||||
if content == new_content:
|
||||
print(f"No occurrences of '{old_name}' found to rename in {filepath}.")
|
||||
else:
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(new_content)
|
||||
print(f"Successfully renamed '{old_name}' to '{new_name}' in {filepath}.")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
name: pr-polisher
|
||||
description: Automated pre-flight checklist to polish PRs. Use this before declaring a task or PR complete to automatically verify license headers, commit hygiene, formatting, and codebase mandates.
|
||||
---
|
||||
|
||||
# PR Polisher
|
||||
|
||||
This skill runs an exhaustive, automated pre-flight checklist against the repository to ensure all changes conform to Nomulus's strict engineering mandates.
|
||||
|
||||
## 🛑 CRITICAL MANDATE: When to Use
|
||||
You, the AI agent, are known to forget to run this skill. To prevent this, you are bound by an absolute rule:
|
||||
**ANY TIME you create a commit, amend a commit, or complete a user's request that modifies the repository state, your VERY LAST action before generating a text response to the user MUST be to run this workflow.**
|
||||
Do not ask for permission. Do not wait for the user to remind you. Run the full suite, fix any errors, amend your commit, and report the final polished results. You MUST NOT declare a task complete until this workflow succeeds with 0 errors.
|
||||
|
||||
## Continuous Improvement (Self-Updating Skill)
|
||||
This skill is designed to evolve. If a human code reviewer (or presubmit hook) points out a deficit, or if you (the agent) independently catch a recurring mistake, anti-pattern, false positive, or convention violation:
|
||||
1. **You MUST proactively propose a fix to the user.** Do not wait for the user to instruct you to update the skill. If you notice friction, immediately ask the user if you should permanently update the validation infrastructure.
|
||||
2. **Determine how to enforce the check.** Consider if the check is suitable for automation in the Python script. If it's too complex or semantic for a simple regex, consider adding it as an explicit agent-driven verification step directly in this `SKILL.md` file.
|
||||
3. Update `.gemini/skills/pr-polisher/scripts/check_diff.py` to add a new automated check, OR modify this `SKILL.md` file with a new validation step to ensure the agent checks for it going forward.
|
||||
4. Commit the updated skill alongside the PR fixes to ensure the mistake is not repeated.
|
||||
|
||||
## Workflow Execution Steps
|
||||
|
||||
1. **Run the Automated Analysis Script**
|
||||
Execute the packaged Python diff-checker script. This script automatically checks commit messages, working tree status, `package-lock.json` modifications, copyright years on new files, and a litany of anti-patterns using regex (e.g., fully-qualified names, incorrect clock injections, generic exception catching).
|
||||
|
||||
```bash
|
||||
python3 ./pr-polisher/scripts/check_diff.py
|
||||
```
|
||||
|
||||
2. **Run Formatting Validation**
|
||||
Always run the project's formatting tools to ensure checkstyle and Google Java Format passes.
|
||||
```bash
|
||||
./gradlew spotlessCheck javaIncrementalFormatCheck
|
||||
# OR if formatting is needed:
|
||||
./gradlew spotlessApply javaIncrementalFormatApply
|
||||
```
|
||||
|
||||
3. **Run Presubmits and Compilation**
|
||||
Ensure that the project builds correctly and all presubmit checks pass. Use scoped builds when possible to save time and avoid unwanted side effects (like modifying `console-webapp/package-lock.json`).
|
||||
```bash
|
||||
# Run presubmits
|
||||
./gradlew runPresubmits
|
||||
|
||||
# Verify compilation (use a scoped build if you only modified one module, e.g., :core)
|
||||
./gradlew :core:build -x test
|
||||
# Run standard test suite if modifying core
|
||||
./gradlew :core:standardTest
|
||||
```
|
||||
|
||||
4. **Verify PR Scope and Extraneous Files (Line-by-Line Inspection)**
|
||||
You must carefully review the entirety of your changes (`git diff HEAD^` or `git diff --cached`). Examine every single file and line changed to explicitly verify that the change *belongs* in this PR. You MUST look for and revert:
|
||||
* **Irrelevant changes:** Formatting or refactoring in files unrelated to the PR's core purpose.
|
||||
* **Accidental files:** Test output files, temp scripts, plan files (e.g., `codebase_review_plan.md`), scratchpads, or anything else generated during your workflow that shouldn't be committed.
|
||||
* **Unintended side effects:** Changes to configuration files like `package-lock.json` unless explicitly required.
|
||||
|
||||
5. **Verify Test Coverage Additions (Line-by-Line Inspection)**
|
||||
While looking at all the diffs, thoroughly check every single line to determine if any test changes or additions are necessary. If you have modified existing logic, added new branches, added a null check, or added new public methods, you MUST manually verify that the corresponding `Test.java` file covers these changes. If the test file or specific test cases do not exist, you must create them. A code review is not thorough if it only checks for compilation.
|
||||
|
||||
6. **Verify Commit Description Accuracy**
|
||||
Re-read your commit message (`git log -1 --pretty=format:%B`). Compare it directly against the actual diff (`git diff HEAD^`) to verify that it completely and accurately describes ONLY the changes present in the commit.
|
||||
* If the scope of the PR changed during prompting, you MUST update the commit message to reflect the final state of the code.
|
||||
* Ensure that the primary purpose of the PR is mentioned first, and that no irrelevant or outdated changes from previous attempts are listed.
|
||||
|
||||
7. **Address Errors, Amend, and Re-Run (Iterative Checking)**
|
||||
If any script throws an error, if scope was reduced, if the commit message was inaccurate, or if formatting changes were applied, you must stage those fixes and amend your commit:
|
||||
```bash
|
||||
git add -u
|
||||
git commit --amend # Or git commit --amend --no-edit if only files changed
|
||||
```
|
||||
**CRITICAL:** You must loop back to Step 1 and run `python3 ./pr-polisher/scripts/check_diff.py` again. Continue this loop of checking and amending until the script definitively returns `0 ERRORS`, the build/presubmits pass, and the working directory is perfectly clean. Do not assume your fixes worked without re-running the check.
|
||||
@@ -0,0 +1,330 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright 2026 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.
|
||||
|
||||
import subprocess
|
||||
import re
|
||||
import sys
|
||||
import datetime
|
||||
|
||||
# Color codes
|
||||
RED = "\03.3[91m"
|
||||
YELLOW = "\03.3[93m"
|
||||
GREEN = "\03.3[92m"
|
||||
RESET = "\03.3[0m"
|
||||
|
||||
errors_found = 0
|
||||
warnings_found = 0
|
||||
|
||||
def log_error(msg):
|
||||
global errors_found
|
||||
errors_found += 1
|
||||
print(f"{RED}[ERROR]{RESET} {msg}")
|
||||
|
||||
def log_warning(msg):
|
||||
global warnings_found
|
||||
warnings_found += 1
|
||||
print(f"{YELLOW}[WARNING]{RESET} {msg}")
|
||||
|
||||
def log_success(msg):
|
||||
print(f"{GREEN}[OK]{RESET} {msg}")
|
||||
|
||||
def run_cmd(cmd):
|
||||
return subprocess.check_output(cmd, shell=True, text=True).strip()
|
||||
|
||||
def check_single_commit():
|
||||
print("--- Checking Commit Count ---")
|
||||
try:
|
||||
count = int(run_cmd("git rev-list --count HEAD ^master"))
|
||||
if count > 1:
|
||||
log_error(f"Branch contains {count} commits ahead of master. All changes for a single PR must be squashed into a single commit.")
|
||||
else:
|
||||
log_success("Branch contains a single commit.")
|
||||
except Exception:
|
||||
# Ignore errors if the git command fails (e.g. not a git repo or no master branch)
|
||||
pass
|
||||
|
||||
def check_commit_message():
|
||||
print("--- Checking Commit Message ---")
|
||||
try:
|
||||
msg = run_cmd("git log -1 --pretty=format:%B")
|
||||
lines = msg.split('\n')
|
||||
subject = lines[0]
|
||||
if len(subject) > 50:
|
||||
log_error(f"Commit subject exceeds 50 characters ({len(subject)} chars): '{subject}'")
|
||||
if not subject[0].isupper():
|
||||
log_error(f"Commit subject must be capitalized: '{subject}'")
|
||||
if subject[-1] in ['.', '!', '?']:
|
||||
log_error(f"Commit subject must not end with punctuation: '{subject}'")
|
||||
|
||||
has_body = False
|
||||
for line in lines[1:]:
|
||||
if line.strip() != "":
|
||||
has_body = True
|
||||
break
|
||||
if not has_body:
|
||||
log_error("Commit message must contain a comprehensive body detailing the 'what' and 'why'.")
|
||||
|
||||
if errors_found == 0:
|
||||
log_success("Commit message format looks good.")
|
||||
except Exception as e:
|
||||
log_error(f"Failed to check commit message: {e}")
|
||||
|
||||
def check_workspace_clean():
|
||||
print("\n--- Checking Workspace State ---")
|
||||
status = run_cmd("git status --porcelain")
|
||||
if status:
|
||||
log_error("Workspace is not clean. Uncommitted changes found:\n" + status)
|
||||
else:
|
||||
log_success("Working directory is clean.")
|
||||
|
||||
def check_package_lock():
|
||||
print("\n--- Checking package-lock.json ---")
|
||||
diff_files = run_cmd("git diff HEAD^ --name-only").split('\n')
|
||||
if "console-webapp/package-lock.json" in diff_files:
|
||||
if "console-webapp/package.json" in diff_files or "dependencies.gradle" in diff_files:
|
||||
log_warning("console-webapp/package-lock.json is modified in the diff. This is expected since dependencies were updated.")
|
||||
else:
|
||||
log_error("console-webapp/package-lock.json is modified in the diff. Unless NPM dependencies were explicitly changed, revert this file using: git checkout console-webapp/package-lock.json")
|
||||
else:
|
||||
log_success("console-webapp/package-lock.json is untouched.")
|
||||
|
||||
def check_license_headers():
|
||||
print("\n--- Checking License Headers on New Files ---")
|
||||
current_year = str(datetime.datetime.now().year)
|
||||
added_files = run_cmd("git diff HEAD^ --name-status --diff-filter=A").split('\n')
|
||||
|
||||
java_header = f"// Copyright {current_year} The Nomulus Authors. All Rights Reserved."
|
||||
script_header = f"# Copyright {current_year} The Nomulus Authors. All Rights Reserved."
|
||||
sql_header = f"-- Copyright {current_year} The Nomulus Authors. All Rights Reserved."
|
||||
ftl_header = f"<#-- Copyright {current_year} The Nomulus Authors. All Rights Reserved."
|
||||
|
||||
files_checked = 0
|
||||
|
||||
for f_line in added_files:
|
||||
if not f_line:
|
||||
continue
|
||||
f = f_line.split('\t')[-1]
|
||||
|
||||
expected_header = None
|
||||
if f.endswith('.java') or f.endswith('.js') or f.endswith('.gradle') or f.endswith('.ts'):
|
||||
expected_header = java_header
|
||||
elif f.endswith('.py') or f.endswith('.sh'):
|
||||
expected_header = script_header
|
||||
elif f.endswith('.sql'):
|
||||
expected_header = sql_header
|
||||
elif f.endswith('.ftl'):
|
||||
expected_header = ftl_header
|
||||
|
||||
if expected_header:
|
||||
files_checked += 1
|
||||
try:
|
||||
with open(f, 'r') as file:
|
||||
content = file.read()
|
||||
if expected_header not in content:
|
||||
log_error(f"Missing or incorrect copyright year in {f}. Expected: {expected_header}")
|
||||
except FileNotFoundError:
|
||||
# File might have been deleted or renamed; ignore missing files.
|
||||
pass
|
||||
|
||||
if files_checked == 0:
|
||||
log_success("No new files requiring license headers added.")
|
||||
|
||||
def check_formatting():
|
||||
print("\n--- Checking Project Formatting ---")
|
||||
try:
|
||||
run_cmd("./gradlew spotlessCheck javaIncrementalFormatCheck")
|
||||
log_success("All formatting checks (spotless and javaIncrementalFormat) passed.")
|
||||
except Exception as e:
|
||||
log_error("Formatting checks failed. Run './gradlew spotlessApply javaIncrementalFormatApply' to fix.")
|
||||
|
||||
def check_diff_anti_patterns():
|
||||
print("\n--- Checking Code Anti-Patterns in Diff ---")
|
||||
diff = run_cmd("git diff HEAD^ -U0")
|
||||
current_file = ""
|
||||
|
||||
# Regex Patterns
|
||||
fqn_pattern = re.compile(r'(?<!import\s)(java|google\.registry|com\.google|org)\.[a-z0-9.]+\.[A-Z][a-zA-Z0-9]+')
|
||||
visibility_pattern = re.compile(r'/\*\s*package\s*\*/')
|
||||
utc_pattern = re.compile(r'ZoneId\.of\("UTC"\)')
|
||||
now_pattern = re.compile(r'(Instant\.now\(\)|OffsetDateTime\.now\(\)|System\.currentTimeMillis\(\))')
|
||||
catch_generic_pattern = re.compile(r'catch\s*\(\s*(Exception|Throwable)\s+[a-zA-Z0-9_]+\s*\)')
|
||||
is_equal_optional_pattern = re.compile(r'\.isEqualTo\(Optional\.of\(')
|
||||
sleep_pattern = re.compile(r'Thread\.sleep\(')
|
||||
suppress_pattern = re.compile(r'@SuppressWarnings\(')
|
||||
wrong_nullable_pattern = re.compile(r'import\s+(?!javax\.annotation\.Nullable;)[a-zA-Z0-9_.]+\.Nullable;')
|
||||
utility_class_pattern = re.compile(r'\b(DateTimeUtils|CacheUtils)\.[a-z]')
|
||||
redundant_tx_pattern = re.compile(r'tm\(\)\.transact\(\s*\(\)\s*->\s*tm\(\)\.reTransact')
|
||||
mutable_collection_pattern = re.compile(r'new\s+(ArrayList|HashMap|HashSet)\s*[<()]')
|
||||
trailing_space_pattern = re.compile(r'[ \t]+$')
|
||||
debug_print_pattern = re.compile(r'(System\.(out|err)\.print|\.printStackTrace\(\))')
|
||||
todo_pattern = re.compile(r'\b(TODO|FIXME)\b')
|
||||
unnecessary_cast_pattern = re.compile(r'\(\s*(?:Instant|ImmutableSet|ImmutableList|ImmutableMap)(?:<[^>]+>)?\s*\)')
|
||||
instant_tostring_pattern = re.compile(r'(?i)(?:instant|time|now|clock\.now\(\))\.toString\(\)')
|
||||
dao_transact_pattern = re.compile(r'tm\(\)\.transact\(')
|
||||
retransact_txtime_pattern = re.compile(r'tm\(\)\.reTransact\(\s*(?:\(\)\s*->\s*)?tm\(\)(?:\.|::)get(?:Transaction|Tx)Time\(\)?\s*\)')
|
||||
inject_command_pattern = re.compile(r'inject\(Command\s+[a-zA-Z0-9_]+\)')
|
||||
clock_now_pattern = re.compile(r'clock\.now\(\)')
|
||||
|
||||
suppress_count = 0
|
||||
|
||||
for line in diff.split('\n'):
|
||||
if line.startswith('+++ b/'):
|
||||
current_file = line[6:]
|
||||
suppress_count = 0
|
||||
continue
|
||||
|
||||
if line.startswith('+') and not line.startswith('+++'):
|
||||
code_line = line[1:]
|
||||
|
||||
# Trailing whitespace
|
||||
if trailing_space_pattern.search(code_line):
|
||||
log_error(f"[{current_file}] Found trailing whitespace.")
|
||||
|
||||
# Skip regex definitions in this script from triggering false positives
|
||||
if 're.compile' not in code_line:
|
||||
# Debug prints
|
||||
if debug_print_pattern.search(code_line):
|
||||
log_error(f"[{current_file}] Found leftover debug print or stack trace (System. out. println / printStackTrace).")
|
||||
|
||||
# TODOs / FIXMEs
|
||||
if todo_pattern.search(code_line):
|
||||
log_warning(f"[{current_file}] Found new T" "ODO or F" "IXME. Ensure this is intentional before completing the PR.")
|
||||
|
||||
if not current_file.endswith('.java'):
|
||||
continue
|
||||
|
||||
# FQN Check
|
||||
fqn_matches = fqn_pattern.findall(code_line)
|
||||
if fqn_matches:
|
||||
# Skip if the match is exactly part of an import or package declaration
|
||||
if not code_line.strip().startswith('import') and not code_line.strip().startswith('package'):
|
||||
log_warning(f"[{current_file}] Potential Fully-Qualified Name found: {fqn_matches}. Use imports instead.")
|
||||
|
||||
# Package visibility
|
||||
if visibility_pattern.search(code_line):
|
||||
log_error(f"[{current_file}] Found '/* package */' modifier. Leave modifier blank instead.")
|
||||
|
||||
# Time zones
|
||||
if utc_pattern.search(code_line):
|
||||
log_error(f"[{current_file}] Found ZoneId.of(\"UTC\"). Use statically imported ZoneOffset.UTC instead.")
|
||||
|
||||
# System clocks
|
||||
if now_pattern.search(code_line):
|
||||
log_error(f"[{current_file}] Found un-injected clock (Instant.now / System.currentTimeMillis). Inject Clock instead.")
|
||||
|
||||
# Catch generic
|
||||
if catch_generic_pattern.search(code_line):
|
||||
log_warning(f"[{current_file}] Catching generic Exception/Throwable. Use specific exceptions.")
|
||||
|
||||
# Truth Optionals
|
||||
if is_equal_optional_pattern.search(code_line):
|
||||
log_warning(f"[{current_file}] Found .isEqualTo(Optional.of(...)). Use Truth's .hasValue(...) instead.")
|
||||
|
||||
# Thread.sleep
|
||||
if sleep_pattern.search(code_line):
|
||||
log_warning(f"[{current_file}] Found Thread.sleep(). Use Sleeper instead in tests.")
|
||||
|
||||
# SuppressWarnings
|
||||
if suppress_pattern.search(code_line):
|
||||
suppress_count += 1
|
||||
if suppress_count > 1:
|
||||
log_error(f"[{current_file}] Multiple @SuppressWarnings detected. They must be merged (e.g. {{\"unchecked\", \"foo\"}}).")
|
||||
else:
|
||||
suppress_count = 0
|
||||
|
||||
# Wrong Nullable
|
||||
if wrong_nullable_pattern.search(code_line):
|
||||
log_error(f"[{current_file}] Found incorrect Nullable import. Always use javax.annotation.Nullable.")
|
||||
|
||||
# Missing static imports for utilities
|
||||
if utility_class_pattern.search(code_line):
|
||||
if not code_line.strip().startswith('import'):
|
||||
log_warning(f"[{current_file}] Found un-statically imported method from DateTimeUtils/CacheUtils. Use static imports.")
|
||||
|
||||
# Redundant transaction wrapping
|
||||
if redundant_tx_pattern.search(code_line):
|
||||
log_error(f"[{current_file}] Found redundant transaction wrapping (tm().transact(() -> tm().reTransact(...))).")
|
||||
|
||||
# Mutable collection instantiation
|
||||
if mutable_collection_pattern.search(code_line):
|
||||
log_warning(f"[{current_file}] Found mutable collection instantiation (ArrayList/HashMap/HashSet). Prefer Guava Immutable collections.")
|
||||
|
||||
# Unnecessary casts
|
||||
if unnecessary_cast_pattern.search(code_line):
|
||||
log_warning(f"[{current_file}] Potential unnecessary cast to Instant or Guava Immutable type. Remove if it compiles without it.")
|
||||
|
||||
# Instant toString
|
||||
if instant_tostring_pattern.search(code_line):
|
||||
log_error(f"[{current_file}] Found potential Instant.toString(). Use DateTimeUtils.formatInstant(...) to preserve .000Z precision.")
|
||||
|
||||
# DAO transactions
|
||||
if current_file.lower().endswith('dao.java') and dao_transact_pattern.search(code_line):
|
||||
log_error(f"[{current_file}] Found tm().transact(...) inside a DAO. Use tm().assertInTransaction() instead.")
|
||||
|
||||
# reTransact around getTxTime in production code
|
||||
if 'src/main/' in current_file and retransact_txtime_pattern.search(code_line):
|
||||
log_error(f"[{current_file}] Unnecessary reTransact() around getTxTime() in production code. Wrap the caller in a transaction instead.")
|
||||
|
||||
# inject(Command)
|
||||
if inject_command_pattern.search(code_line):
|
||||
log_error(f"[{current_file}] Generic inject(Command) methods do not work with Dagger. Use explicit concrete types.")
|
||||
|
||||
# clock.now() in tests
|
||||
if 'src/test/' in current_file and clock_now_pattern.search(code_line):
|
||||
log_warning(f"[{current_file}] Prefer using a fixed, static constant Instant over capturing clock.now() in tests to prevent flakiness.")
|
||||
|
||||
def check_missing_tests():
|
||||
print("\n--- Checking for Missing Tests ---")
|
||||
diff_files = run_cmd("git diff HEAD^ --name-only").split('\n')
|
||||
main_files_modified = False
|
||||
test_files_modified = False
|
||||
|
||||
for f in diff_files:
|
||||
if f.startswith('core/src/main/') or f.startswith('util/src/main/'):
|
||||
if f.endswith('.java'):
|
||||
main_files_modified = True
|
||||
if f.startswith('core/src/test/') or f.startswith('util/src/test/'):
|
||||
if f.endswith('Test.java'):
|
||||
test_files_modified = True
|
||||
|
||||
if main_files_modified and not test_files_modified:
|
||||
log_warning("Production code (.java files in src/main/) was modified, but no corresponding tests (.java files in src/test/) were added or updated. You MUST proactively write tests for any new logic or bug fixes.")
|
||||
else:
|
||||
log_success("Test coverage check passed (tests were included or no production Java code was changed).")
|
||||
|
||||
def main():
|
||||
print("========================================")
|
||||
print(" NOMULUS PR POLISHER CHECKLIST ")
|
||||
print("========================================\n")
|
||||
|
||||
check_single_commit()
|
||||
check_commit_message()
|
||||
check_workspace_clean()
|
||||
check_package_lock()
|
||||
check_license_headers()
|
||||
check_missing_tests()
|
||||
check_formatting()
|
||||
check_diff_anti_patterns()
|
||||
|
||||
print("\n========================================")
|
||||
if errors_found == 0 and warnings_found == 0:
|
||||
print(f"{GREEN}SUCCESS: All checks passed. PR is polished!{RESET}")
|
||||
else:
|
||||
print(f"RESULTS: {RED}{errors_found} ERRORS{RESET}, {YELLOW}{warnings_found} WARNINGS{RESET}")
|
||||
print("Please address the above issues before declaring the PR complete.")
|
||||
sys.exit(1 if errors_found > 0 else 0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,48 @@
|
||||
name: Request Lockfile Review
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
branches: ["master"]
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
review-lockfiles:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
# We intentionally do NOT use actions/checkout here.
|
||||
# This keeps the environment completely secure and satisfies CodeQL.
|
||||
|
||||
- name: Check files via GitHub API
|
||||
id: check_files
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
|
||||
// Get the list of files in the PR directly from the API
|
||||
const { data: files } = await github.rest.pulls.listFiles({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber,
|
||||
per_page: 100
|
||||
});
|
||||
|
||||
// Look for any file **ending** in gradle.lockfile
|
||||
const hasLockfile = files.some(file => file.filename.endsWith('gradle.lockfile'));
|
||||
core.setOutput('has_lockfile', hasLockfile ? 'true' : 'false');
|
||||
|
||||
- name: Post unresolved review comment
|
||||
if: steps.check_files.outputs.has_lockfile == 'true'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
await github.rest.pulls.createReview({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.payload.pull_request.number,
|
||||
event: 'REQUEST_CHANGES',
|
||||
body: `### ⚠️ Attention Required: Lockfile Detected\nThis pull request contains modifications to one or more \`*.lockfile\` files. Please confirm that you have run update_dependency.sh to push new dependencies to the private repo.\n\n_Someone with Admin role must manually dismiss this review before merging._`
|
||||
});
|
||||
@@ -44,30 +44,58 @@ This document outlines foundational mandates, architectural patterns, and projec
|
||||
- **Test Helpers & Timestamps:** If a static test helper method (like in `DatabaseHelper`) needs the database transaction time but might be called from outside a transaction, using `tm().reTransact(tm()::getTxTime)` is acceptable. However, NEVER wrap it redundantly like `tm().transact(() -> tm().reTransact(tm()::getTxTime))`. If you are just setting an arbitrary timestamp in a test where the exact DB transaction time isn't strictly required, prefer `Instant.now()` or `clock.now()` to avoid creating unnecessary database transactions.
|
||||
- **Production Code:** In production code, if a flow fails because it is calling `getTxTime()` outside of a transaction, you must wrap the *caller* in a transaction instead of adding an unnecessary `reTransact()` around `getTxTime()`.
|
||||
- **Transactional Time:** Ensure code that relies on `tm().getTransactionTime()` (or `tm().getTxTime()`) is executed within a transaction context.
|
||||
- **Database Schema Migrations & 2-PR Split Mandate:**
|
||||
- **Mandatory Consultation of `db/README.md`:** Before planning, drafting, or executing any database schema modifications (e.g., adding/altering columns, creating Flyway `.sql` migration scripts, or modifying JPA entity `@Column` mappings), you **MUST read and strictly adhere to `db/README.md`**.
|
||||
- **Strict 2-PR Deployment Split:** Never propose or submit combining Flyway SQL scripts (`db/src/main/resources/sql/flyway/V*.sql`) and Java ORM changes (`.java` entity files + `db-schema.sql.generated`) into a single PR for submission to `master`. Because live servers during a rolling deployment will fail if Java code attempts to access unmigrated database columns/constraints, all schema additions must be split into two sequential PRs per `db/README.md`:
|
||||
1. **PR #1 (Database Schema Only):** Contains *only* the new Flyway `.sql` script, the `flyway.txt` index update (`:db:generateFlywayIndex`), the `nomulus.golden.sql` dump (`:nom:generate_golden_file`), and ER diagrams (`er_diagram/`). Must contain **zero `.java` files or `db-schema.sql.generated` changes**.
|
||||
2. **PR #2 (Java ORM, EPP Flows & Generated Schema Map):** Submitted *only after* PR #1 is deployed to production. Contains all `.java` entity/flow modifications, tests, and the regenerated `db-schema.sql.generated` (`generateSqlSchema`).
|
||||
|
||||
### 5. Testing Best Practices
|
||||
- **Mandatory Proactive Testing:** You MUST automatically write and update tests alongside your code changes WITHOUT waiting for the user to prompt you. If you add a new feature, fix a bug, or change core logic, you are explicitly required to identify the corresponding `*Test.java` file and implement comprehensive test coverage for your changes.
|
||||
- **FakeClock and Sleeper:** Use `FakeClock` and `Sleeper` for any logic involving timeouts, delays, or expiration.
|
||||
- **Empirical Reproduction:** Before fixing a bug, always create a test case that reproduces the failure.
|
||||
- **Base Classes:** Leverage `CommandTestCase`, `EppToolCommandTestCase`, etc., to reduce boilerplate and ensure consistent setup (e.g., clock initialization).
|
||||
- **Gradle Test Patterns:** When running tests to investigate fixes in the "core" directory, try to first use the "standardTest" Gradle task. It is faster than the "test" task, which includes the "fragileTest" task. Only run the full "test" task after "standardTest" succeeds.
|
||||
- **Mandatory SQL Integration Verification:** Whenever you modify any database schema, Flyway script (`.sql`), or JPA entity class, you MUST explicitly run both `./gradlew :db:test` and `./gradlew :core:sqlIntegrationTest` **in addition to** the standard test suites (`./gradlew standardTest` or `./gradlew test`) before finalizing the task or declaring completion. Do not rely solely on `standardTest` when database schemas or ORM mappings are touched; all three test suites (`standardTest`, `:db:test`, and `:core:sqlIntegrationTest`) are mandatory.
|
||||
|
||||
### 6. Project Dependencies
|
||||
- **Common Module:** When using `Clock` or other core utilities in a new or separate module (like `load-testing`), ensure `implementation project(':common')` is added to the module's `build.gradle`.
|
||||
- **Updating Dependencies:** When updating third-party dependencies, you MUST follow this exact, non-negotiable sequence. NEVER run the update script without first verifying the lockfiles locally.
|
||||
1. Modify `dependencies.gradle`.
|
||||
2. Run `./gradlew dependencies --write-locks --update-locks '*:*'` to regenerate all Gradle lockfiles locally to resolve new versions.
|
||||
3. Run `./gradlew clean build` to exhaustively compile and run all tests against the new transitive dependency tree. If there are compilation or test errors due to breaking API changes or output formatting changes in the new dependency version, your primary goal is to **fix our codebase** to be compliant with the new dependency version. Only revert the dependency bump or cap the version if the required code changes are prohibitively complex or outside the scope of the current task.
|
||||
4. Only once the build passes and `git status` shows modified lockfiles, commit the `dependencies.gradle` and lockfile changes.
|
||||
5. ONLY after the local changes are committed and verified, execute the internal script `/google/src/head/depot/google3/domain/registry/tools/update_dependency.sh`. This ensures you don't corrupt the upstream remote artifact cache with broken or missing lockfiles.
|
||||
6. **Crucial Final Verification:** The `update_dependency.sh` script runs its own internal Blaze resolution and often modifies or drops additional `buildscript-gradle.lockfile` files. After the script finishes successfully, run `git status`. If there are any untracked or modified `*.lockfile` files, stage them and run `git commit --amend --no-edit` to ensure your commit perfectly reflects the final state deployed to the SSO repository.
|
||||
|
||||
### 7. Search and Discovery
|
||||
- **No CodeSearch:** This project is hosted on GitHub, not Google3. Do NOT use `mcp_Coding_search_for_files_codesearch` or other internal Google3 search tools.
|
||||
- **Local Grep:** Use local shell commands like `git grep` or `grep` via `run_shell_command` to search the codebase.
|
||||
|
||||
### 8. Git Operation Boundaries & Remote Push Prohibition
|
||||
- **Zero-Bundling Rule for Remote Transfers:** NEVER bundle remote transfer commands (`git push`, `repo upload`, `g4 upload`, `g4 mail`, `hg push`, `jj git push`) inside compound bash pipelines (`&&`, `||`, `;`) with local staging or commit commands (e.g., `git commit --amend && git push ...`). Every remote transfer command MUST be executed as a standalone, single-command `run_command` invocation.
|
||||
- **Mandatory Two-Step Handover Protocol:** When working on pull requests, changelists, or branches, strictly halt right at the local repository boundary (`git commit` / `g4 change`). Report the local commit SHA, `git status`, and `git diff` verification to the user, stop calling tools, and explicitly prompt: *"Local commit `[SHA]` is verified. Do you authorize running `git push origin [branch]` (or `--force-with-lease` when force-pushing an amended branch) to update the remote repository?"*
|
||||
- **Absolute Prohibition on Unsolicited Pushes:** NEVER propose or run any remote synchronization or push command (`git push`, `repo upload`, `g4 upload`) on your own volition without explicit, unambiguous textual authorization in the immediate turn from the user.
|
||||
- **Mandatory GitHub PR Description & Reviewable Synchronization:** Whenever pushing an amended commit to a branch associated with an active pull request, the agent MUST immediately synchronize the PR description on GitHub (`gh pr edit`). To do this safely:
|
||||
1. Check `gh pr view --json body` first to inspect existing content.
|
||||
2. Explicitly preserve any existing Reviewable bot links (`This change is https://reviewable.io/reviews/...`) when updating the body rather than blindly overwriting the entire description.
|
||||
|
||||
## Performance and Efficiency
|
||||
- **Turn Minimization:** Aim for "perfect" code in the first iteration. Iterative fixes for checkstyle or compilation errors consume significant context and time.
|
||||
- **Context Management:** Use sub-agents for batch refactoring or high-volume output tasks to keep the main session history lean and efficient.
|
||||
- **Code Formatting:** Do not write custom Python scripts or manual regex replacements to fix code formatting issues (e.g., unused imports, import ordering, line length). Instead, use the project's built-in formatting tools: run `./gradlew spotlessApply` to fix unused/unordered imports and `./gradlew javaIncrementalFormatApply` (or `google-java-format --replace <files>`) to automatically fix Java formatting and indentation errors.
|
||||
- **Programmatic Measurement for Markdown:** When writing Markdown files (`.md`)—and especially when formatting or line-wrapping them—you **MUST** explicitly use programmatic functions (such as `awk '{print length($0)}'` or Python `len(line)`) to measure the exact string length of every line from column 1, rather than making assumptions based on what appears in your context window.
|
||||
|
||||
## General Code Review Lessons & Avoidable Mistakes
|
||||
Based on historical PR reviews, avoid the following common mistakes:
|
||||
- **No Unnecessary Casts:** Do not unnecessarily cast objects if the method signature accepts the type directly (e.g., avoid `(Instant) fakeClock.now()` or `(ImmutableSet<String>) bsaQuery(...)` if it compiles without it).
|
||||
- **Visibility Modifiers:** Do not use `/* package */` comments to denote package-private visibility. Just leave the modifier blank; it is an established idiom in this codebase.
|
||||
|
||||
## Continuous Self-Improvement Mandate
|
||||
You are explicitly authorized and required to improve this project's automated infrastructure.
|
||||
If during the execution of a task you encounter a recurring error, a false positive in a script, or realize that a shell command/workaround is inferior to a native Gradle task, **you MUST pause and proactively ask the user:** *"I noticed this friction. Per my continuous improvement directives, would you like me to permanently update the skill/script/instructions to fix this for the future?"*
|
||||
Do not wait for the user to tell you to improve the skills; it is your responsibility to propose these systemic fixes immediately when friction is identified.
|
||||
|
||||
### Advanced Java & Guava Idioms
|
||||
- **Immutable Types:** Declare variables, fields, and return types explicitly as Guava immutable types (e.g., `ImmutableList<T>`, `ImmutableMap<K, V>`) instead of their generic interfaces (`List<T>`, `Map<K, V>`) to clearly communicate immutability contracts to callers. Use `toImmutableList()` and `toImmutableMap()` collectors in streams rather than manually accumulating into an `ArrayList` or `HashMap`.
|
||||
- **Constructors:** Do not perform heavy logic, I/O, or external API calls inside constructors. Initialization logic should be deferred or handled in a factory method or a dedicated startup routine.
|
||||
@@ -81,7 +109,7 @@ Based on historical PR reviews, avoid the following common mistakes:
|
||||
|
||||
# Gemini Engineering Guide: Nomulus Codebase
|
||||
|
||||
This document captures high-level architectural patterns, lessons learned from large-scale refactorings (like the Joda-Time to `java.time` migration), and specific instructions to avoid common pitfalls in this environment.
|
||||
This document captures high-level architectural patterns, lessons learned from large-scale refactorings, and specific instructions to avoid common pitfalls in this environment.
|
||||
|
||||
## 🏛 Architecture Overview
|
||||
|
||||
@@ -92,6 +120,7 @@ This document captures high-level architectural patterns, lessons learned from l
|
||||
- Core boundaries: `DateTimeUtils.START_INSTANT` (Unix Epoch) and `DateTimeUtils.END_INSTANT` (Long.MAX_VALUE / 1000).
|
||||
|
||||
## Source Control
|
||||
- **No Unprompted Pushing:** You MUST NEVER push your changes to a remote repository (e.g., `git push origin ...`) unless the user VERY EXPLICITLY instructs you to do so. If you have finished a task, simply state that it is committed locally and ready for review. Do not assume pushing is desired.
|
||||
- **Committing:** Always create a new commit on the branch if one hasn't been created yet for the branch's specific work. Only perform amending (`git commit --amend --no-edit`) for subsequent changes once the initial commit has been successfully created.
|
||||
- **One Commit Per PR:** All changes for a single PR must be squashed into a single commit before merging.
|
||||
- **Default to Amend:** Once an initial commit is created for a PR, all subsequent functional changes should be amended into that same commit by default (`git commit --amend --no-edit`). This ensures the PR remains a single, clean unit of work throughout the development lifecycle.
|
||||
@@ -104,8 +133,13 @@ This document captures high-level architectural patterns, lessons learned from l
|
||||
5. **Only after** step 4 has successfully returned a clean working directory may you generate a text response to the user declaring that the task is complete.
|
||||
- **Diff Review:** Before finalizing a task, review the full diff (e.g., `git diff HEAD^`) to ensure all changes are functional and relevant. Identify and revert any formatting-only changes in files that do not contain functional updates to keep the commit focused.
|
||||
|
||||
## Self-Review Guidelines
|
||||
Before finalizing any PR or declaring a task complete, you MUST perform a thorough, rigorous self-review of your entire diff. Run `git diff HEAD^` (or review the staged changes) and actively verify the following against every modified line:
|
||||
## 🛑 CRITICAL: MANDATORY PR POLISHER EXECUTION
|
||||
You, the AI agent, have a known failure mode of forgetting to run the PR polisher after making changes because you focus too narrowly on the immediate task. To prevent this, you are bound by the following absolute rule:
|
||||
|
||||
**ANY TIME you create a commit, amend a commit, or complete a user's request that modifies the repository state, your VERY LAST action before generating a text response to the user MUST be to execute the `pr-polisher` skill.**
|
||||
Do not ask for permission. Do not wait for the user to remind you. Run the full suite (`python3 .gemini/skills/pr-polisher/scripts/check_diff.py`, `runPresubmits`, `build`, and `test`), fix any errors, amend your commit, and report the final polished results in your response.
|
||||
|
||||
You MUST NOT declare a task done until this automated script returns 0 errors, the presubmits and tests pass, and the workspace is fully clean.
|
||||
|
||||
1. **Imports & FQNs:** Did I leave any fully-qualified class names or static variables inline? Did I add the necessary imports for them? *Crucial Exception:* If the file already imports a class with the identical name (e.g., it uses both `java.util.Date` and `java.sql.Date`), one MUST remain fully qualified to avoid a compilation conflict.
|
||||
2. **Diff Scope:** Are there any formatting-only changes in files that I did not functionally modify? If so, revert them. Does the total line count of the diff align with the approved scope (e.g., ~1,000 lines for migrations)?
|
||||
@@ -113,7 +147,7 @@ Before finalizing any PR or declaring a task complete, you MUST perform a thorou
|
||||
4. **Missing Tests & Coverage:** *Perform a structured check for any new methods or modified behavior.* Did I add a new utility method (like `plusMonths(Instant, int)`) or change core logic? If so, I MUST open the corresponding test file and write tests to cover the new functionality (including edge cases, negative values, and leap years) before considering the task complete. A code review is not thorough if it only checks for compilation. I must actively ensure every new branch of logic has a test.
|
||||
5. **Package Lock:** Did I include `console-webapp/package-lock.json` in my diff? If so, I MUST revert it (`git checkout console-webapp/package-lock.json`) unless I explicitly intended to modify NPM dependencies. This file is often modified by the build process and should not be committed accidentally.
|
||||
|
||||
Only after actively confirming these checks against your diff are you permitted to finalize the task.
|
||||
Only after actively executing the `pr-polisher` skill and confirming these checks against your diff are you permitted to finalize the task.
|
||||
|
||||
## Refactoring & Migration Guardrails
|
||||
|
||||
@@ -152,3 +186,4 @@ This protocol defines the standard for interacting with GitHub repositories and
|
||||
- **One Commit Per PR:** Ensure all changes are squashed into a single, clean commit. Use `git commit --amend --no-edit` for follow-up fixes.
|
||||
- **Clean Workspace:** Always run `git status` and verify the repository state before declaring a task complete.
|
||||
- **Package Lock:** The Gradle build automatically modifies `console-webapp/package-lock.json` via the `npmInstallDeps` task. ALWAYS revert this file (`git checkout console-webapp/package-lock.json`) before staging changes or finalizing a commit unless you explicitly modified NPM dependencies.
|
||||
- **PR Description Synchronization:** Whenever you amend or update a commit's description, you **MUST** check whether the corresponding GitHub PR description (if one exists) matches the previous commit description. If the PR description was not manually customized (i.e., it simply reflects the older commit description), you must automatically update it via `gh pr edit` to keep it in sync with your newly updated commit description—**strictly after the updated commit has been pushed to the remote GitHub PR branch**. Do not update the remote PR description when changes are only committed locally. **CRITICAL:** When updating the PR description, you must strictly preserve any automated review links or footer blocks (such as Reviewable link blocks: `<!-- Reviewable:start -->...<!-- Reviewable:end -->`) at the bottom of the description.
|
||||
|
||||
@@ -12,7 +12,7 @@ Nomulus is an open source, scalable, cloud-based service for operating
|
||||
[top-level domains](https://en.wikipedia.org/wiki/Top-level_domain) (TLDs). It
|
||||
is the authoritative source for the TLDs that it runs, meaning that it is
|
||||
responsible for tracking domain name ownership and handling registrations,
|
||||
renewals, availability checks, and WHOIS requests. End-user registrants (i.e.,
|
||||
renewals, availability checks, and RDAP requests. End-user registrants (i.e.,
|
||||
people or companies that want to register a domain name) use an intermediate
|
||||
domain name registrar acting on their behalf to interact with the registry.
|
||||
|
||||
@@ -97,7 +97,7 @@ Nomulus has the following capabilities:
|
||||
for details), and an implementation based on
|
||||
[Google Cloud Secret Manager](https://cloud.google.com/security/products/secret-manager) is
|
||||
available.
|
||||
* **TPC Proxy**: Nomulus is built on top of the [Jetty](https://jetty.org/)
|
||||
* **TCP Proxy**: Nomulus is built on top of the [Jetty](https://jetty.org/)
|
||||
container that implements the [Jakarta Servlet](https://jakarta.ee/specifications/servlet/)
|
||||
specification and only serves HTTP/S traffic. A proxy to translate raw TCP traffic (e.g., EPP)
|
||||
to and from HTTP is provided.
|
||||
|
||||
+21
-1
@@ -395,7 +395,7 @@ subprojects {
|
||||
// expose to users.
|
||||
if (project.name != 'jetty' && !services.contains(project.path)) {
|
||||
javadocSource << project.sourceSets.main.allJava
|
||||
javadocClasspath << { project.sourceSets.main.runtimeClasspath.files }
|
||||
javadocClasspath << { project.sourceSets.main.compileClasspath.files }
|
||||
javadocClasspath << "${buildDir}/generated/sources/annotationProcessor/java/main"
|
||||
if (project.tasks.findByName('compileJava')) {
|
||||
javadocDependentTasks << project.tasks.compileJava
|
||||
@@ -609,3 +609,23 @@ gradle.taskGraph.whenReady { graph ->
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task fastBuild {
|
||||
group = 'build'
|
||||
description = 'A lightweight build for local dev. Compiles Java, runs standard tests, and checks formatting, but skips Docker images, fragile tests, and the massive Angular console builds. (Do not use this target to verify console changes.)'
|
||||
|
||||
dependsOn build
|
||||
// Remove the heavy default dependencies specifically for fastBuild
|
||||
gradle.taskGraph.whenReady { graph ->
|
||||
if (graph.hasTask(fastBuild)) {
|
||||
project(':console-webapp').tasks.named('buildConsoleForAll').get().enabled = false
|
||||
project(':jetty').tasks.named('buildNomulusImage').get().enabled = false
|
||||
project(':core').tasks.named('buildToolImage').get().enabled = false
|
||||
project(':core').tasks.named('fragileTest').get().enabled = false
|
||||
project(':jetty').tasks.named('stage').get().enabled = false
|
||||
if (project.tasks.findByName('stage') != null) {
|
||||
project.tasks.named('stage').get().enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ dependencies {
|
||||
implementation deps['com.google.code.findbugs:jsr305']
|
||||
implementation deps['com.google.guava:guava']
|
||||
implementation deps['jakarta.inject:jakarta.inject-api']
|
||||
implementation deps['org.freemarker:freemarker']
|
||||
implementation deps['com.google.flogger:flogger']
|
||||
implementation deps['io.github.java-diff-utils:java-diff-utils']
|
||||
implementation deps['com.google.truth:truth']
|
||||
|
||||
@@ -34,7 +34,7 @@ commons-collections:commons-collections:3.2.2=checkstyle
|
||||
info.picocli:picocli:4.7.7=checkstyle
|
||||
io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,testAnnotationProcessor,testingAnnotationProcessor
|
||||
io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,testAnnotationProcessor,testingAnnotationProcessor
|
||||
io.github.java-diff-utils:java-diff-utils:4.16=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
||||
io.github.java-diff-utils:java-diff-utils:4.17=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
||||
jakarta.inject:jakarta.inject-api:2.0.1=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
||||
javax.inject:javax.inject:1=annotationProcessor,testAnnotationProcessor,testingAnnotationProcessor
|
||||
junit:junit:4.13.2=testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
||||
@@ -60,6 +60,7 @@ org.codehaus.plexus:plexus-classworlds:2.6.0=checkstyle
|
||||
org.codehaus.plexus:plexus-component-annotations:2.1.0=checkstyle
|
||||
org.codehaus.plexus:plexus-container-default:2.1.0=checkstyle
|
||||
org.codehaus.plexus:plexus-utils:3.3.0=checkstyle
|
||||
org.freemarker:freemarker:2.3.34=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
||||
org.hamcrest:hamcrest-core:1.3=testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
||||
org.jacoco:org.jacoco.agent:0.8.14=jacocoAgent,jacocoAnt
|
||||
org.jacoco:org.jacoco.ant:0.8.14=jacocoAnt
|
||||
@@ -67,12 +68,12 @@ org.jacoco:org.jacoco.core:0.8.14=jacocoAnt
|
||||
org.jacoco:org.jacoco.report:0.8.14=jacocoAnt
|
||||
org.javassist:javassist:3.28.0-GA=checkstyle
|
||||
org.jspecify:jspecify:1.0.0=annotationProcessor,checkstyle,compileClasspath,deploy_jar,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath,testing,testingAnnotationProcessor,testingCompileClasspath
|
||||
org.junit.jupiter:junit-jupiter-api:5.13.4=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-engine:5.13.4=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-commons:1.13.4=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-engine:1.13.4=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-launcher:1.13.4=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit:junit-bom:5.13.4=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-api:6.1.2=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-engine:6.1.2=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-commons:6.1.2=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-engine:6.1.2=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-launcher:6.1.2=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit:junit-bom:6.1.2=testCompileClasspath,testRuntimeClasspath
|
||||
org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.ow2.asm:asm-commons:9.9=jacocoAnt
|
||||
org.ow2.asm:asm-tree:9.9=jacocoAnt
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright 2026 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.util;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import freemarker.core.HTMLOutputFormat;
|
||||
import freemarker.template.Configuration;
|
||||
import freemarker.template.Template;
|
||||
import freemarker.template.TemplateExceptionHandler;
|
||||
import jakarta.inject.Inject;
|
||||
import java.io.StringWriter;
|
||||
|
||||
/**
|
||||
* A utility class for rendering FreeMarker templates.
|
||||
*
|
||||
* <p>This renderer is configured to use HTML as the default output format, which enables automatic
|
||||
* escaping of all interpolated variables. It also uses the "computer" number format to ensure
|
||||
* consistent formatting of numeric values across different locales.
|
||||
*/
|
||||
public class TemplateRenderer {
|
||||
|
||||
private final Configuration configuration;
|
||||
|
||||
@Inject
|
||||
public TemplateRenderer() {
|
||||
this.configuration = new Configuration(Configuration.VERSION_2_3_32);
|
||||
this.configuration.setClassLoaderForTemplateLoading(getClass().getClassLoader(), "");
|
||||
this.configuration.setDefaultEncoding("UTF-8");
|
||||
this.configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
|
||||
this.configuration.setLogTemplateExceptions(false);
|
||||
this.configuration.setWrapUncheckedExceptions(true);
|
||||
this.configuration.setFallbackOnNullLoopVariable(false);
|
||||
this.configuration.setOutputFormat(HTMLOutputFormat.INSTANCE);
|
||||
this.configuration.setNumberFormat("computer");
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the specified template with the given data model.
|
||||
*
|
||||
* @param templatePath the path to the template file relative to the classpath root
|
||||
* @param dataModel an immutable map containing the data to be used in the template
|
||||
* @return the rendered template as a string
|
||||
* @throws RuntimeException if the template cannot be found, parsed, or processed
|
||||
*/
|
||||
public String render(String templatePath, ImmutableMap<String, Object> dataModel) {
|
||||
try {
|
||||
Template template = configuration.getTemplate(templatePath);
|
||||
StringWriter writer = new StringWriter();
|
||||
template.process(dataModel, writer);
|
||||
return writer.toString();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(String.format("Error rendering template %s", templatePath), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright 2026 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.util;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link TemplateRenderer}. */
|
||||
class TemplateRendererTest {
|
||||
|
||||
private final TemplateRenderer renderer = new TemplateRenderer();
|
||||
|
||||
@Test
|
||||
void testRender_success() {
|
||||
ImmutableMap<String, Object> data =
|
||||
ImmutableMap.of(
|
||||
"name", "World", "score", 42, "showMessage", true, "message", "Keep going!");
|
||||
String result = renderer.render("google/registry/util/test_template.ftl", data);
|
||||
assertThat(result).isEqualTo("Hello World!\nYour score is 42.\nMessage: Keep going!\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRender_conditional_false() {
|
||||
ImmutableMap<String, Object> data =
|
||||
ImmutableMap.of("name", "User", "score", 0, "showMessage", false);
|
||||
String result = renderer.render("google/registry/util/test_template.ftl", data);
|
||||
assertThat(result).isEqualTo("Hello User!\nYour score is 0.\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRender_htmlEscaping() {
|
||||
ImmutableMap<String, Object> data =
|
||||
ImmutableMap.of("name", "<b>World</b>", "score", 42, "showMessage", false);
|
||||
String result = renderer.render("google/registry/util/test_template.ftl", data);
|
||||
assertThat(result).contains("Hello <b>World</b>!");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRender_missingTemplate_throwsException() {
|
||||
assertThrows(
|
||||
RuntimeException.class,
|
||||
() -> renderer.render("non/existent/template.ftl", ImmutableMap.of()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRender_missingVariable_throwsException() {
|
||||
// The template expects 'name', 'score', and 'showMessage', but the map is empty.
|
||||
assertThrows(
|
||||
RuntimeException.class,
|
||||
() -> renderer.render("google/registry/util/test_template.ftl", ImmutableMap.of()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRender_unusedVariable_ignored() {
|
||||
ImmutableMap<String, Object> data =
|
||||
ImmutableMap.of(
|
||||
"name",
|
||||
"User",
|
||||
"score",
|
||||
100,
|
||||
"showMessage",
|
||||
false,
|
||||
"unusedKey",
|
||||
"This should be ignored");
|
||||
String result = renderer.render("google/registry/util/test_template.ftl", data);
|
||||
assertThat(result).isEqualTo("Hello User!\nYour score is 100.\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<#-- Copyright 2026 The Nomulus Authors. All Rights Reserved. -->
|
||||
Hello ${name}!
|
||||
Your score is ${score}.
|
||||
<#if showMessage>
|
||||
Message: ${message}
|
||||
</#if>
|
||||
+3
-30
@@ -90,15 +90,15 @@ PRESUBMITS = {
|
||||
# License check
|
||||
PresubmitCheck(
|
||||
r".*Copyright 20\d{2} The Nomulus Authors\. All Rights Reserved\.",
|
||||
("java", "js", "soy", "sql", "py", "sh", "gradle", "ts"), {
|
||||
("java", "js", "sql", "py", "sh", "gradle", "ts", "ftl"), {
|
||||
".git", "/build/", "node_modules/", "LoggerConfig.java", "registrar_bin.",
|
||||
"registrar_dbg.", "google-java-format-diff.py",
|
||||
"nomulus.golden.sql", "soyutils_usegoog.js", "javascript/checks.js"
|
||||
"nomulus.golden.sql", "javascript/checks.js"
|
||||
}, REQUIRED):
|
||||
"File did not include the license header.",
|
||||
|
||||
# Files must end in a newline
|
||||
PresubmitCheck(r".*\n$", ("java", "js", "soy", "sql", "py", "sh", "gradle", "ts", "xml"),
|
||||
PresubmitCheck(r".*\n$", ("java", "js", "sql", "py", "sh", "gradle", "ts", "xml", "ftl"),
|
||||
{"node_modules/", ".idea"}, REQUIRED):
|
||||
"Source files must end in a newline.",
|
||||
|
||||
@@ -127,33 +127,6 @@ PRESUBMITS = {
|
||||
"System.(out|err).println is only allowed in tools/ packages. Please "
|
||||
"use a logger instead.",
|
||||
|
||||
# Various Soy linting checks
|
||||
PresubmitCheck(
|
||||
r".* (/\*)?\* {?@param ",
|
||||
"soy",
|
||||
{},
|
||||
):
|
||||
"In SOY please use the ({@param name: string} /** User name. */) style"
|
||||
" parameter passing instead of the ( * @param name User name.) style "
|
||||
"parameter passing.",
|
||||
PresubmitCheck(
|
||||
r'.*\{[^}]+\w+:\s+"',
|
||||
"soy",
|
||||
{},
|
||||
):
|
||||
"Please don't use double-quoted string literals in Soy parameters",
|
||||
PresubmitCheck(
|
||||
r'.*autoescape\s*=\s*"[^s]',
|
||||
"soy",
|
||||
{},
|
||||
):
|
||||
"All soy templates must use strict autoescaping",
|
||||
PresubmitCheck(
|
||||
r".*noAutoescape",
|
||||
"soy",
|
||||
{},
|
||||
):
|
||||
"All soy templates must use strict autoescaping",
|
||||
PresubmitCheck(
|
||||
r".*\nimport\s+(?:static\s+)?.*\.shaded\..*",
|
||||
"java",
|
||||
|
||||
@@ -9,15 +9,14 @@ expected to change.
|
||||
|
||||
## Deployment
|
||||
|
||||
The webapp is deployed with the nomulus default service war to GKE.
|
||||
During nomulus default service war build task, gradle script triggers the
|
||||
following:
|
||||
The webapp is deployed as part of the default Nomulus GKE service image.
|
||||
During the image build task, the Gradle script triggers the following:
|
||||
|
||||
1) Console webapp build script `buildConsoleWebapp`, which installs
|
||||
dependencies, assembles a compiled ts -> js, minified, optimized static
|
||||
artifact (html, css, js)
|
||||
2) Artifact assembled in step 1 then gets copied to core project web artifact
|
||||
location, so that it can be deployed with the rest of the core webapp
|
||||
2) Artifact assembled in step 1 then gets copied to the jetty webapp resource
|
||||
location, so that it can be staged inside the default GKE service container.
|
||||
|
||||
## Development server
|
||||
|
||||
|
||||
@@ -21,27 +21,23 @@ clean {
|
||||
|
||||
task npmInstallDeps(type: Exec) {
|
||||
workingDir "${consoleDir}/"
|
||||
executable 'npm'
|
||||
args 'i', '--no-audit', '--no-fund', '--loglevel=error'
|
||||
commandLine 'sh', '-c', 'npm i --no-audit --no-fund --loglevel=error'
|
||||
}
|
||||
|
||||
task runConsoleWebappLocally(type: Exec) {
|
||||
workingDir "${consoleDir}/"
|
||||
executable 'npm'
|
||||
args 'run', 'start:dev'
|
||||
commandLine 'sh', '-c', 'npm run start:dev'
|
||||
}
|
||||
|
||||
task runConsoleWebappUnitTests(type: Exec) {
|
||||
workingDir "${consoleDir}/"
|
||||
executable 'npm'
|
||||
args 'run', 'test'
|
||||
commandLine 'sh', '-c', 'npm run test'
|
||||
}
|
||||
|
||||
task buildConsoleWebapp(type: Exec) {
|
||||
workingDir "${consoleDir}/"
|
||||
executable 'npm'
|
||||
def configuration = project.getProperty('configuration')
|
||||
args 'run', "build", "--configuration=${configuration}"
|
||||
commandLine 'sh', '-c', "npx ng build --base-href=/console/ --configuration=${configuration} --output-path=staged/dist"
|
||||
doFirst {
|
||||
println "Building console for environment: ${configuration}"
|
||||
}
|
||||
@@ -52,8 +48,7 @@ task buildConsoleForAll() {}
|
||||
def createConsoleTask = { env ->
|
||||
project.tasks.register("buildConsoleFor${env.capitalize()}", Exec) {
|
||||
workingDir "${consoleDir}/"
|
||||
executable 'npm'
|
||||
args 'run', 'build', "--configuration=${env}"
|
||||
commandLine 'sh', '-c', "npx ng build --base-href=/console/ --configuration=${env}"
|
||||
doFirst {
|
||||
println "Building console for environment: ${env}"
|
||||
}
|
||||
@@ -91,14 +86,12 @@ tasks.buildConsoleWebapp.mustRunAfter(tasks.buildConsoleForProduction)
|
||||
|
||||
task applyFormatting(type: Exec) {
|
||||
workingDir "${consoleDir}/"
|
||||
executable 'npm'
|
||||
args 'run', 'prettify'
|
||||
commandLine 'sh', '-c', 'npm run prettify'
|
||||
}
|
||||
|
||||
task checkFormatting(type: Exec) {
|
||||
workingDir "${consoleDir}/"
|
||||
executable 'npm'
|
||||
args 'run', 'prettify:check'
|
||||
commandLine 'sh', '-c', 'npm run prettify:check'
|
||||
}
|
||||
|
||||
tasks.buildConsoleWebapp.dependsOn(tasks.npmInstallDeps)
|
||||
@@ -107,3 +100,4 @@ tasks.applyFormatting.dependsOn(tasks.npmInstallDeps)
|
||||
tasks.checkFormatting.dependsOn(tasks.npmInstallDeps)
|
||||
tasks.build.dependsOn(tasks.checkFormatting)
|
||||
tasks.build.dependsOn(tasks.runConsoleWebappUnitTests)
|
||||
tasks.build.dependsOn(tasks.buildConsoleForAll)
|
||||
|
||||
Generated
-123
@@ -628,18 +628,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/build/node_modules/@types/node": {
|
||||
"version": "25.7.0",
|
||||
"resolved": "https://us-npm.pkg.dev/artifact-foundry-prod/ah-3p-staging-npm/@types/node/-/node-25.7.0.tgz",
|
||||
"integrity": "sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/build/node_modules/@vitejs/plugin-basic-ssl": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.1.0.tgz",
|
||||
@@ -653,24 +641,6 @@
|
||||
"vite": "^6.0.0 || ^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/build/node_modules/chokidar": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://us-npm.pkg.dev/artifact-foundry-prod/ah-3p-staging-npm/chokidar/-/chokidar-5.0.0.tgz",
|
||||
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"readdirp": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/build/node_modules/magic-string": {
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||
@@ -694,22 +664,6 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/build/node_modules/readdirp": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://us-npm.pkg.dev/artifact-foundry-prod/ah-3p-staging-npm/readdirp/-/readdirp-5.0.0.tgz",
|
||||
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/build/node_modules/rxjs": {
|
||||
"version": "7.8.2",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||
@@ -743,15 +697,6 @@
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/build/node_modules/undici-types": {
|
||||
"version": "7.21.0",
|
||||
"resolved": "https://us-npm.pkg.dev/artifact-foundry-prod/ah-3p-staging-npm/undici-types/-/undici-types-7.21.0.tgz",
|
||||
"integrity": "sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@angular/build/node_modules/vite": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz",
|
||||
@@ -971,24 +916,6 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/cli/node_modules/chokidar": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://us-npm.pkg.dev/artifact-foundry-prod/ah-3p-staging-npm/chokidar/-/chokidar-5.0.0.tgz",
|
||||
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"readdirp": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/cli/node_modules/cli-spinners": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.4.0.tgz",
|
||||
@@ -1092,22 +1019,6 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/cli/node_modules/readdirp": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://us-npm.pkg.dev/artifact-foundry-prod/ah-3p-staging-npm/readdirp/-/readdirp-5.0.0.tgz",
|
||||
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/cli/node_modules/rxjs": {
|
||||
"version": "7.8.2",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||
@@ -4695,24 +4606,6 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@schematics/angular/node_modules/chokidar": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://us-npm.pkg.dev/artifact-foundry-prod/ah-3p-staging-npm/chokidar/-/chokidar-5.0.0.tgz",
|
||||
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"readdirp": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@schematics/angular/node_modules/cli-spinners": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.4.0.tgz",
|
||||
@@ -4816,22 +4709,6 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/@schematics/angular/node_modules/readdirp": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://us-npm.pkg.dev/artifact-foundry-prod/ah-3p-staging-npm/readdirp/-/readdirp-5.0.0.tgz",
|
||||
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@schematics/angular/node_modules/rxjs": {
|
||||
"version": "7.8.2",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||
|
||||
@@ -48,7 +48,11 @@ interface DomainData {
|
||||
selector: 'app-response-dialog',
|
||||
template: `
|
||||
<h2 mat-dialog-title>{{ data.title }}</h2>
|
||||
<mat-dialog-content [innerHTML]="data.content" />
|
||||
<mat-dialog-content>
|
||||
@for (line of data.content; track line) {
|
||||
<div>{{ line }}</div>
|
||||
}
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<button mat-button (click)="onClose()">Close</button>
|
||||
</mat-dialog-actions>
|
||||
@@ -59,7 +63,7 @@ export class ResponseDialogComponent {
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<ReasonDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
public data: { title: string; content: string }
|
||||
public data: { title: string; content: string[] }
|
||||
) {}
|
||||
|
||||
onClose(): void {
|
||||
@@ -312,11 +316,13 @@ export class DomainListComponent {
|
||||
this.dialog.open(ResponseDialogComponent, {
|
||||
data: {
|
||||
title: 'Domain Deletion Results',
|
||||
content: `Successfully deleted - ${successCount} domain(s)<br/>Failed to delete - ${failureCount} domain(s)<br/>${
|
||||
content: [
|
||||
`Successfully deleted - ${successCount} domain(s)`,
|
||||
`Failed to delete - ${failureCount} domain(s)`,
|
||||
failureCount
|
||||
? 'Some domains could not be deleted due to ongoing processes or server errors. '
|
||||
: ''
|
||||
}Please check the table for more information.`,
|
||||
? 'Some domains could not be deleted due to ongoing processes or server errors. Please check the table for more information.'
|
||||
: 'Please check the table for more information.',
|
||||
],
|
||||
},
|
||||
});
|
||||
this.selection.clear();
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<p>
|
||||
<mat-label for="password">Password: </mat-label>
|
||||
<mat-form-field name="password" appearance="outline">
|
||||
<input matInput type="text" formControlName="password" required />
|
||||
<input matInput type="password" formControlName="password" required />
|
||||
</mat-form-field>
|
||||
</p>
|
||||
<p>
|
||||
@@ -60,7 +60,7 @@
|
||||
<p>
|
||||
<mat-label for="password">Password: </mat-label>
|
||||
<mat-form-field name="password" appearance="outline">
|
||||
<input matInput type="text" formControlName="password" required />
|
||||
<input matInput type="password" formControlName="password" required />
|
||||
</mat-form-field>
|
||||
</p>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { Component, computed } from '@angular/core';
|
||||
import { FormControl, FormGroup } from '@angular/forms';
|
||||
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { RegistrarService } from '../registrar/registrar.service';
|
||||
import { UserDataService } from '../shared/services/userData.service';
|
||||
@@ -42,11 +42,11 @@ export class RegistryLockComponent {
|
||||
];
|
||||
|
||||
lockDomain = new FormGroup({
|
||||
password: new FormControl(''),
|
||||
password: new FormControl('', [Validators.required]),
|
||||
});
|
||||
|
||||
unlockDomain = new FormGroup({
|
||||
password: new FormControl(''),
|
||||
password: new FormControl('', [Validators.required]),
|
||||
relockTime: new FormControl(undefined),
|
||||
});
|
||||
|
||||
|
||||
@@ -50,7 +50,10 @@ export class NewOteComponent {
|
||||
|
||||
createOte = new FormGroup({
|
||||
registrarId: new FormControl('', [Validators.required]),
|
||||
registrarEmail: new FormControl('', [Validators.required]),
|
||||
registrarEmail: new FormControl('', [
|
||||
Validators.required,
|
||||
Validators.email,
|
||||
]),
|
||||
});
|
||||
|
||||
constructor(
|
||||
|
||||
@@ -67,8 +67,10 @@ export default class NewRegistrarComponent {
|
||||
onBillingAccountMapChange(val: String) {
|
||||
const billingAccountMap: { [key: string]: string } = {};
|
||||
this.newRegistrar.billingAccountMap = val.split('\n').reduce((acc, val) => {
|
||||
const [currency, billingCode] = val.split('=');
|
||||
acc[currency] = billingCode;
|
||||
const [currency, billingCode] = val.split('=').map((s) => s?.trim());
|
||||
if (currency && billingCode) {
|
||||
acc[currency] = billingCode;
|
||||
}
|
||||
return acc;
|
||||
}, billingAccountMap);
|
||||
}
|
||||
|
||||
@@ -97,10 +97,9 @@
|
||||
@for (column of columns; track column.columnDef) {
|
||||
<mat-list-item role="listitem">
|
||||
<span class="console-app__list-key">{{ column.header }} </span>
|
||||
<span
|
||||
class="console-app__list-value"
|
||||
[innerHTML]="column.cell(registrarInEdit).replace('<br/>', ' ')"
|
||||
></span>
|
||||
<span class="console-app__list-value">{{
|
||||
column.cell(registrarInEdit)
|
||||
}}</span>
|
||||
</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ export class RegistrarDetailsComponent implements OnInit {
|
||||
}
|
||||
|
||||
checkOteStatus() {
|
||||
this.router.navigate(['ote-status/', this.registrarInEdit.registrarId], {
|
||||
this.router.navigate(['ote-status', this.registrarInEdit.registrarId], {
|
||||
queryParamsHandling: 'merge',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -49,10 +49,9 @@
|
||||
<mat-header-cell *matHeaderCellDef>
|
||||
{{ column.header }}
|
||||
</mat-header-cell>
|
||||
<mat-cell
|
||||
*matCellDef="let row"
|
||||
[innerHTML]="column.cell(row)"
|
||||
></mat-cell>
|
||||
<mat-cell *matCellDef="let row" style="white-space: pre-wrap">{{
|
||||
column.cell(row)
|
||||
}}</mat-cell>
|
||||
</ng-container>
|
||||
}
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
|
||||
@@ -56,7 +56,7 @@ export const columns = [
|
||||
cell: (record: Registrar) =>
|
||||
`${Object.entries(record.billingAccountMap || {}).reduce(
|
||||
(acc, [key, val]) => {
|
||||
return `${acc}${key}=${val}<br/>`;
|
||||
return `${acc}${key}=${val}\n`;
|
||||
},
|
||||
''
|
||||
)}`,
|
||||
|
||||
@@ -25,7 +25,18 @@
|
||||
@for (column of columns; track column) {
|
||||
<ng-container [matColumnDef]="column.columnDef">
|
||||
<mat-header-cell *matHeaderCellDef> {{ column.header }} </mat-header-cell>
|
||||
<mat-cell *matCellDef="let row" [innerHTML]="column.cell(row)"></mat-cell>
|
||||
<mat-cell *matCellDef="let row">
|
||||
@if (column.columnDef === 'name') {
|
||||
<div class="contact__name-column">
|
||||
<div class="contact__name-column-title">{{ row.name }}</div>
|
||||
<div class="contact__name-column-roles">
|
||||
{{ row.userFriendlyTypes.join(" • ") }}
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
{{ column.cell(row) }}
|
||||
}
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
}
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
|
||||
@@ -34,14 +34,7 @@ export default class ContactComponent {
|
||||
{
|
||||
columnDef: 'name',
|
||||
header: 'Name',
|
||||
cell: (contact: ViewReadyContact) => `
|
||||
<div class="contact__name-column">
|
||||
<div class="contact__name-column-title">${contact.name}</div>
|
||||
<div class="contact__name-column-roles">${contact.userFriendlyTypes.join(
|
||||
' • '
|
||||
)}</div>
|
||||
</div>
|
||||
`,
|
||||
cell: (contact: ViewReadyContact) => `${contact.name}`,
|
||||
},
|
||||
{
|
||||
columnDef: 'emailAddress',
|
||||
|
||||
+3
-3
@@ -9,7 +9,7 @@
|
||||
<mat-label>Old password: </mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="text"
|
||||
type="password"
|
||||
formControlName="oldPassword"
|
||||
required
|
||||
autocomplete="current-password"
|
||||
@@ -25,7 +25,7 @@
|
||||
<mat-label>New password: </mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="text"
|
||||
type="password"
|
||||
formControlName="newPassword"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
@@ -40,7 +40,7 @@
|
||||
<mat-label>Confirm new password: </mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="text"
|
||||
type="password"
|
||||
formControlName="newPasswordRepeat"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
|
||||
@@ -288,8 +288,9 @@ export class BackendService {
|
||||
verifyRegistryLockRequest(
|
||||
lockVerificationCode: string
|
||||
): Observable<RegistryLockVerificationResponse> {
|
||||
return this.http.get<RegistryLockVerificationResponse>(
|
||||
`/console-api/registry-lock-verify?lockVerificationCode=${lockVerificationCode}`
|
||||
return this.http.post<RegistryLockVerificationResponse>(
|
||||
`/console-api/registry-lock-verify?lockVerificationCode=${lockVerificationCode}`,
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<mat-header-cell *matHeaderCellDef>
|
||||
{{ column.header }}
|
||||
</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row" [innerHTML]="column.cell(row)"></mat-cell>
|
||||
<mat-cell *matCellDef="let row">{{ column.cell(row) }}</mat-cell>
|
||||
</ng-container>
|
||||
}
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
|
||||
+24
-89
@@ -53,14 +53,7 @@ def dockerIncompatibleTestPatterns = [
|
||||
// affected by global states outside of Nomulus classes, e.g., threads and
|
||||
// objects retained by frameworks.
|
||||
// TODO(weiminyu): identify cause and fix offending tests.
|
||||
def fragileTestPatterns = [
|
||||
// Breaks random other tests when running with standardTests.
|
||||
"google/registry/bsa/UploadBsaUnavailableDomainsActionTest.*",
|
||||
// Currently changes a global configuration parameter that for some reason
|
||||
// results in timestamp inversions for other tests. TODO(mmuller): fix.
|
||||
"google/registry/flows/host/HostInfoFlowTest.*",
|
||||
"google/registry/beam/common/RegistryPipelineWorkerInitializerTest.*",
|
||||
] + dockerIncompatibleTestPatterns
|
||||
def fragileTestPatterns = dockerIncompatibleTestPatterns
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
@@ -94,7 +87,6 @@ processTestResources {
|
||||
|
||||
configurations {
|
||||
jaxb
|
||||
soy
|
||||
devtool
|
||||
|
||||
nonprodImplementation.extendsFrom implementation
|
||||
@@ -120,7 +112,7 @@ configurations {
|
||||
// For reasons we do not understand, marking the following dependencies as
|
||||
// compileOnly instead of compile does not exclude them from runtimeClasspath.
|
||||
all {
|
||||
// servlet-api:3.1 pulled in but not used by soy compiler
|
||||
// servlet-api:3.1 pulled in but not used
|
||||
exclude group: 'javax.servlet', module: 'javax.servlet-api'
|
||||
}
|
||||
}
|
||||
@@ -134,6 +126,7 @@ dependencies {
|
||||
implementation deps['com.google.api:gax']
|
||||
implementation deps['com.google.api.grpc:proto-google-common-protos']
|
||||
implementation deps['com.google.api.grpc:proto-google-cloud-secretmanager-v1']
|
||||
implementation deps['com.google.apis:google-api-services-cloudkms']
|
||||
implementation deps['com.google.api-client:google-api-client']
|
||||
implementation deps['com.google.api-client:google-api-client-servlet']
|
||||
implementation deps['com.google.monitoring-client:metrics']
|
||||
@@ -166,10 +159,6 @@ dependencies {
|
||||
implementation deps['com.google.flogger:flogger']
|
||||
implementation deps['com.google.guava:guava']
|
||||
implementation deps['com.google.protobuf:protobuf-java']
|
||||
// Might need to add this back if we re-add nebula-lint
|
||||
// gradleLint.ignore('unused-dependency') {
|
||||
implementation deps['com.google.gwt:gwt-user']
|
||||
// }
|
||||
implementation deps['com.google.cloud:google-cloud-compute']
|
||||
implementation deps['com.google.cloud:google-cloud-core']
|
||||
implementation deps['com.google.cloud:google-cloud-storage']
|
||||
@@ -181,9 +170,8 @@ dependencies {
|
||||
implementation deps['com.google.oauth-client:google-oauth-client-jetty']
|
||||
implementation deps['com.google.oauth-client:google-oauth-client-servlet']
|
||||
implementation deps['com.google.re2j:re2j']
|
||||
implementation deps['com.google.template:soy']
|
||||
implementation deps['com.googlecode.json-simple:json-simple']
|
||||
implementation deps['com.jcraft:jsch']
|
||||
implementation deps['org.freemarker:freemarker']
|
||||
implementation deps['com.github.mwiede:jsch']
|
||||
implementation deps['com.zaxxer:HikariCP']
|
||||
implementation deps['com.squareup.okhttp3:okhttp']
|
||||
implementation deps['dnsjava:dnsjava']
|
||||
@@ -242,9 +230,9 @@ dependencies {
|
||||
runtimeOnly deps['org.slf4j:slf4j-jdk14']
|
||||
testImplementation deps['org.testcontainers:jdbc']
|
||||
testImplementation deps['org.testcontainers:junit-jupiter']
|
||||
implementation deps['org.testcontainers:postgresql']
|
||||
nonprodImplementation deps['org.testcontainers:postgresql']
|
||||
testImplementation deps['org.testcontainers:selenium']
|
||||
testImplementation deps['org.testcontainers:testcontainers']
|
||||
nonprodImplementation deps['org.testcontainers:testcontainers']
|
||||
implementation deps['redis.clients:jedis']
|
||||
implementation deps['us.fatehi:schemacrawler']
|
||||
implementation deps['us.fatehi:schemacrawler-api']
|
||||
@@ -257,6 +245,7 @@ dependencies {
|
||||
implementation project(':common')
|
||||
testImplementation project(path: ':common', configuration: 'testing')
|
||||
implementation project(':util')
|
||||
implementation project(':networking')
|
||||
// Import NomulusPostreSql from ':db' for implementation but exclude dependencies.
|
||||
implementation project(path: ':db', configuration: 'implementationApi')
|
||||
nonprodRuntimeOnly project(':db')
|
||||
@@ -286,9 +275,20 @@ dependencies {
|
||||
testImplementation deps['org.junit-pioneer:junit-pioneer']
|
||||
testImplementation deps['org.junit.platform:junit-platform-runner']
|
||||
testImplementation deps['org.junit.platform:junit-platform-suite-api']
|
||||
testImplementation deps['org.junit.platform:junit-platform-suite']
|
||||
testImplementation deps['org.mockito:mockito-core']
|
||||
testImplementation deps['org.mockito:mockito-junit-jupiter']
|
||||
|
||||
implementation deps['io.netty:netty-buffer']
|
||||
implementation deps['io.netty:netty-codec-http']
|
||||
implementation deps['io.netty:netty-codec']
|
||||
implementation deps['io.netty:netty-common']
|
||||
implementation deps['io.netty:netty-handler']
|
||||
implementation deps['io.netty:netty-transport']
|
||||
implementation deps['redis.clients:jedis']
|
||||
runtimeOnly deps['io.netty:netty-tcnative-boringssl-static']
|
||||
implementation deps['org.yaml:snakeyaml']
|
||||
|
||||
// Indirect dependency found by undeclared-dependency check. Such
|
||||
// dependencies should go after all other implementation and testImplementation
|
||||
// dependencies to avoid overriding them accidentally.
|
||||
@@ -299,9 +299,6 @@ dependencies {
|
||||
jaxb deps['org.glassfish.jaxb:jaxb-runtime']
|
||||
jaxb deps['org.glassfish.jaxb:jaxb-xjc']
|
||||
|
||||
// Dependency needed for soy to java compilation.
|
||||
soy deps['com.google.template:soy']
|
||||
|
||||
// Flyway classes needed to generate the golden file.
|
||||
implementation deps['org.flywaydb:flyway-core']
|
||||
implementation deps['org.flywaydb:flyway-database-postgresql']
|
||||
@@ -371,62 +368,7 @@ task jaxbToJava {
|
||||
}
|
||||
}
|
||||
|
||||
task soyToJava {
|
||||
// Relative paths of soy directories.
|
||||
def spec11SoyDir = "google/registry/reporting/spec11/soy"
|
||||
def toolsSoyDir = "google/registry/tools/soy"
|
||||
|
||||
def soyRelativeDirs = [spec11SoyDir, toolsSoyDir]
|
||||
soyRelativeDirs.each {
|
||||
inputs.dir "${resourcesSourceDir}/${it}"
|
||||
outputs.dir "${generatedDir}/${it}"
|
||||
}
|
||||
|
||||
ext.soyToJava = { javaPackage, outputDirectory, soyFiles ->
|
||||
project.services.get(ExecOperations).javaexec {
|
||||
mainClass = "com.google.template.soy.SoyParseInfoGenerator"
|
||||
classpath = configurations.soy
|
||||
jvmArgs = ["--sun-misc-unsafe-memory-access=allow", "--enable-native-access=ALL-UNNAMED"]
|
||||
args = ["--javaPackage", "${javaPackage}",
|
||||
"--outputDirectory", "${outputDirectory}",
|
||||
"--javaClassNameSource", "filename",
|
||||
"--srcs", "${soyFiles.join(',')}"]
|
||||
}
|
||||
|
||||
// Replace the "@link" tags after the "@deprecated" tags in the generated
|
||||
// files. The soy compiler doesn't generate imports for these, causing
|
||||
// us to get warnings when we generate javadocs.
|
||||
// TODO(b/200296387): To be fair, the deprecations are accurate: we're
|
||||
// using the old "SoyInfo" classes instead of the new "Templates" files.
|
||||
// When we convert to the new classes, this hack can go away.
|
||||
def outputs = fileTree(outputDirectory) {
|
||||
include '**/*.java'
|
||||
}
|
||||
|
||||
outputs.each { file ->
|
||||
project.services.get(ExecOperations).exec {
|
||||
commandLine = ['sed', '-i""', '-e', 's/@link/LINK/g', file.getCanonicalPath()]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
doLast {
|
||||
soyToJava('google.registry.tools.soy',
|
||||
"${generatedDir}/${toolsSoyDir}",
|
||||
fileTree(
|
||||
dir: "${resourcesSourceDir}/${toolsSoyDir}",
|
||||
include: ['**/*.soy']))
|
||||
|
||||
soyToJava('google.registry.reporting.spec11.soy',
|
||||
"${generatedDir}/${spec11SoyDir}",
|
||||
fileTree(
|
||||
dir: "${resourcesSourceDir}/${spec11SoyDir}",
|
||||
include: ['**/*.soy']))
|
||||
}
|
||||
}
|
||||
|
||||
compileJava.dependsOn jaxbToJava
|
||||
compileJava.dependsOn soyToJava
|
||||
|
||||
// Make testing artifacts available to be depended up on by other projects.
|
||||
// TODO: factor out google.registry.testing to be a separate project.
|
||||
@@ -512,7 +454,7 @@ project.tasks.create('generateSqlSchema', JavaExec) {
|
||||
mainClass = 'google.registry.tools.DevTool'
|
||||
jvmArgs "--sun-misc-unsafe-memory-access=allow"
|
||||
args = [
|
||||
'-e', 'alpha',
|
||||
'-e', 'unittest',
|
||||
'generate_sql_schema', '--start_postgresql', '-o',
|
||||
"${rootProject.projectRootDir}/db/src/main/resources/sql/schema/" +
|
||||
"db-schema.sql.generated"
|
||||
@@ -764,10 +706,7 @@ task fragileTest(type: FilteringTest) {
|
||||
|
||||
// Dedicated test suite for schema-dependent tests.
|
||||
task sqlIntegrationTest(type: FilteringTest) {
|
||||
// TestSuite still requires a JUnit 4 runner, which knows how to handle JUnit 5 tests.
|
||||
// Here we need to override parent's choice of JUnit 5. If changing this, remember to
|
||||
// change :integration:sqlIntegrationTest too.
|
||||
useJUnit()
|
||||
useJUnitPlatform()
|
||||
excludeTestCases = false
|
||||
tests = ['google/registry/schema/integration/SqlIntegrationTestSuite.*']
|
||||
|
||||
@@ -814,13 +753,9 @@ test {
|
||||
// Don't run any tests from this task, all testing gets done in the
|
||||
// FilteringTest tasks.
|
||||
exclude "**"
|
||||
// TODO(weiminyu): Remove dependency on sqlIntegrationTest
|
||||
}.dependsOn(fragileTest, standardTest, registryToolIntegrationTest, sqlIntegrationTest)
|
||||
}.dependsOn(standardTest, registryToolIntegrationTest, sqlIntegrationTest, fragileTest)
|
||||
|
||||
// When we override tests, we also break the cleanTest command.
|
||||
cleanTest.dependsOn(cleanFragileTest, cleanStandardTest,
|
||||
cleanRegistryToolIntegrationTest, cleanSqlIntegrationTest)
|
||||
cleanTest.dependsOn(cleanStandardTest, cleanRegistryToolIntegrationTest, cleanSqlIntegrationTest, cleanFragileTest)
|
||||
|
||||
project.build.dependsOn devtool
|
||||
project.build.dependsOn buildToolImage
|
||||
project.build.dependsOn ':stage'
|
||||
project.build.dependsOn devtool, buildToolImage
|
||||
|
||||
+319
-342
@@ -1,23 +1,22 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
aopalliance:aopalliance:1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
|
||||
args4j:args4j:2.33=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
|
||||
args4j:args4j:2.33=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.charleskorn.kaml:kaml:0.20.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson.core:jackson-core:2.20.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson.core:jackson-databind:2.20.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.20.2=testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.20.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson.datatype:jackson-datatype-joda:2.20.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.20.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson:jackson-bom:2.20.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.woodstox:woodstox-core:7.0.0=testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.22=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson.core:jackson-core:2.22.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson.core:jackson-databind:2.22.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.22.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.22.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson.datatype:jackson-datatype-joda:2.22.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.22.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson:jackson-bom:2.22.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.woodstox:woodstox-core:7.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.github.ben-manes.caffeine:caffeine:3.0.5=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
|
||||
com.github.ben-manes.caffeine:caffeine:3.2.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.github.docker-java:docker-java-api:3.4.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.github.docker-java:docker-java-transport-zerodep:3.4.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.github.docker-java:docker-java-transport:3.4.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.github.docker-java:docker-java-api:3.4.2=nonprodCompileClasspath,nonprodRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.github.docker-java:docker-java-transport-zerodep:3.4.2=nonprodCompileClasspath,nonprodRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.github.docker-java:docker-java-transport:3.4.2=nonprodCompileClasspath,nonprodRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.github.jnr:jffi:1.3.15=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.github.jnr:jnr-a64asm:1.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.github.jnr:jnr-constants:0.10.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
@@ -27,81 +26,84 @@ com.github.jnr:jnr-posix:3.1.22=compileClasspath,deploy_jar,nonprodCompileClassp
|
||||
com.github.jnr:jnr-unixsocket:0.38.25=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.github.jnr:jnr-x86asm:1.0.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
|
||||
com.github.mwiede:jsch:2.28.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.android:annotations:4.1.1.4=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.api-client:google-api-client-jackson2:2.0.1=compileClasspath,nonprodCompileClasspath,testCompileClasspath
|
||||
com.google.api-client:google-api-client-jackson2:2.7.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.api-client:google-api-client-gson:2.9.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api-client:google-api-client-java6:2.1.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api-client:google-api-client-servlet:2.7.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.api-client:google-api-client-servlet:2.9.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
|
||||
com.google.api-client:google-api-client:2.9.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:gapic-google-cloud-storage-v2:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
com.google.api.grpc:gapic-google-cloud-storage-v2:2.68.0=testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:3.21.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1:0.193.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta2:0.193.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:grpc-google-cloud-bigtable-v2:2.73.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:grpc-google-cloud-pubsub-v1:1.130.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:grpc-google-cloud-spanner-admin-database-v1:6.111.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:grpc-google-cloud-spanner-admin-instance-v1:6.111.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:grpc-google-cloud-spanner-v1:6.111.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:grpc-google-cloud-storage-control-v2:2.44.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:grpc-google-cloud-storage-v2:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
com.google.api.grpc:grpc-google-cloud-storage-v2:2.68.0=testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:grpc-google-common-protos:2.65.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1:3.21.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1alpha:3.21.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta1:0.193.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta2:0.193.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta:3.21.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-bigtable-admin-v2:2.73.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-bigtable-v2:2.73.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-compute-v1:1.82.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-datastore-v1:0.125.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-firestore-v1:3.37.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-monitoring-v3:3.85.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-pubsub-v1:1.130.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-secretmanager-v1:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-secretmanager-v1beta2:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-spanner-admin-database-v1:6.111.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-spanner-admin-instance-v1:6.111.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-spanner-v1:6.111.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-storage-control-v2:2.44.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-storage-v2:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-storage-v2:2.68.0=testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-tasks-v2:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-tasks-v2beta2:0.141.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-tasks-v2beta3:0.141.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-common-protos:2.60.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-iam-v1:1.60.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
com.google.api.grpc:proto-google-iam-v1:1.66.0=testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api:api-common:2.57.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
com.google.api:api-common:2.63.0=testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api:gax-grpc:2.74.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
com.google.api:gax-grpc:2.80.0=testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api:gax-httpjson:2.74.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
com.google.api:gax-httpjson:2.80.0=testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api:gax:2.74.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.apis:google-api-services-admin-directory:directory_v1-rev20260227-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:gapic-google-cloud-storage-v2:2.68.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:gapic-google-cloud-storage-v2:2.70.0=testCompileClasspath
|
||||
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:3.28.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1:0.200.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta2:0.200.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:grpc-google-cloud-bigtable-v2:2.78.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:grpc-google-cloud-pubsub-v1:1.132.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:grpc-google-cloud-spanner-admin-database-v1:6.117.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:grpc-google-cloud-spanner-admin-instance-v1:6.117.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:grpc-google-cloud-spanner-v1:6.117.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:grpc-google-cloud-storage-control-v2:2.49.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:grpc-google-cloud-storage-v2:2.68.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:grpc-google-cloud-storage-v2:2.70.0=testCompileClasspath
|
||||
com.google.api.grpc:grpc-google-common-protos:2.71.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1:3.28.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1alpha:3.28.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta1:0.200.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta2:0.200.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta:3.28.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-bigtable-admin-v2:2.78.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-bigtable-v2:2.78.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-compute-v1:1.104.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-datastore-v1:0.133.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-firestore-v1:3.42.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-logging-v2:0.122.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-monitoring-v3:3.93.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-pubsub-v1:1.132.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-secretmanager-v1:2.51.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-secretmanager-v1:2.94.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
|
||||
com.google.api.grpc:proto-google-cloud-secretmanager-v1beta1:2.94.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
|
||||
com.google.api.grpc:proto-google-cloud-secretmanager-v1beta2:2.51.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-secretmanager-v1beta2:2.94.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
|
||||
com.google.api.grpc:proto-google-cloud-spanner-admin-database-v1:6.117.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-spanner-admin-instance-v1:6.117.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-spanner-v1:6.117.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-storage-control-v2:2.49.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-storage-v2:2.68.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-storage-v2:2.70.0=testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-tasks-v2:2.51.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-tasks-v2:2.94.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
|
||||
com.google.api.grpc:proto-google-cloud-tasks-v2beta2:0.141.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-tasks-v2beta2:2.94.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
|
||||
com.google.api.grpc:proto-google-cloud-tasks-v2beta3:0.141.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-cloud-tasks-v2beta3:2.94.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
|
||||
com.google.api.grpc:proto-google-common-protos:2.73.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api.grpc:proto-google-iam-v1:1.66.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath
|
||||
com.google.api.grpc:proto-google-iam-v1:1.68.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api:api-common:2.65.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api:gax-grpc:2.82.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api:gax-httpjson:2.82.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.api:gax:2.82.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.apis:google-api-services-admin-directory:directory_v1-rev20260531-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.apis:google-api-services-bigquery:v2-rev20251012-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.apis:google-api-services-cloudkms:v1-rev20260702-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.apis:google-api-services-cloudresourcemanager:v1-rev20250606-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.apis:google-api-services-dataflow:v1b3-rev20260405-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.apis:google-api-services-dns:v1-rev20260421-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.apis:google-api-services-drive:v3-rev20260428-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.apis:google-api-services-gmail:v1-rev20260427-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.apis:google-api-services-dataflow:v1b3-rev20260615-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.apis:google-api-services-dns:v1-rev20260616-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.apis:google-api-services-drive:v3-rev20260712-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.apis:google-api-services-gmail:v1-rev20260525-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.apis:google-api-services-groupssettings:v1-rev20220614-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.apis:google-api-services-healthcare:v1-rev20240130-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.apis:google-api-services-iam:v2-rev20250502-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.apis:google-api-services-iamcredentials:v1-rev20211203-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.apis:google-api-services-monitoring:v3-rev20260129-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.apis:google-api-services-pubsub:v1-rev20220904-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.apis:google-api-services-sheets:v4-rev20260213-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.apis:google-api-services-sqladmin:v1beta4-rev20260317-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.apis:google-api-services-storage:v1-rev20251118-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
com.google.apis:google-api-services-storage:v1-rev20260204-2.0.0=testCompileClasspath,testRuntimeClasspath
|
||||
com.google.auth:google-auth-library-credentials:1.46.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
com.google.auth:google-auth-library-credentials:1.47.0=testCompileClasspath,testRuntimeClasspath
|
||||
com.google.auth:google-auth-library-oauth2-http:1.46.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
com.google.auth:google-auth-library-oauth2-http:1.47.0=testCompileClasspath,testRuntimeClasspath
|
||||
com.google.apis:google-api-services-sheets:v4-rev20260610-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.apis:google-api-services-sqladmin:v1beta4-rev20260529-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.apis:google-api-services-storage:v1-rev20260204-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
com.google.apis:google-api-services-storage:v1-rev20260524-2.0.0=testCompileClasspath,testRuntimeClasspath
|
||||
com.google.auth:google-auth-library-credentials:1.49.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.auth:google-auth-library-oauth2-http:1.49.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.auto.service:auto-service-annotations:1.0.1=nonprodAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.auto.service:auto-service-annotations:1.1.1=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.auto.service:auto-service:1.1.1=annotationProcessor
|
||||
@@ -109,81 +111,77 @@ com.google.auto.value:auto-value-annotations:1.11.0=compileClasspath,deploy_jar,
|
||||
com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.auto.value:auto-value:1.11.1=annotationProcessor,deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testRuntimeClasspath
|
||||
com.google.auto:auto-common:1.2.2=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.cloud.bigdataoss:gcsio:2.2.26=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud.bigdataoss:util:2.2.26=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud.bigdataoss:gcsio:3.1.16=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud.bigdataoss:util:3.1.16=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud.bigtable:bigtable-client-core-config:1.28.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud.datastore:datastore-v1-proto-client:2.34.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud.datastore:datastore-v1-proto-client:3.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud.opentelemetry:detector-resources-support:0.33.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud.opentelemetry:exporter-metrics:0.33.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud.opentelemetry:shared-resourcemapping:0.33.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.cloud.sql:jdbc-socket-factory-core:1.28.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud.sql:postgres-socket-factory:1.28.3=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-bigquerystorage:3.21.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-bigtable:2.73.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-compute:1.82.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-core-grpc:2.64.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
com.google.cloud:google-cloud-core-grpc:2.70.0=testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-core-http:2.54.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
com.google.cloud:google-cloud-core-http:2.70.0=testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-core:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-firestore:3.37.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-monitoring:3.85.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-nio:0.132.0=testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-pubsub:1.148.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-secretmanager:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-spanner:6.111.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-storage-control:2.44.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-storage:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-tasks:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:grpc-gcp:1.9.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud.sql:jdbc-socket-factory-core:1.28.6=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud.sql:postgres-socket-factory:1.28.6=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.cloud.sql:schemas:1.28.6=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-bigquerystorage:3.28.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-bigtable:2.78.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-compute:1.104.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-core-grpc:2.70.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
com.google.cloud:google-cloud-core-grpc:2.72.0=testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-core-http:2.70.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-core-http:2.72.0=testCompileClasspath
|
||||
com.google.cloud:google-cloud-core:2.70.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
com.google.cloud:google-cloud-core:2.72.0=testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-firestore:3.42.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-logging:3.33.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-monitoring:3.93.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-nio:0.127.24=testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-nio:0.134.0=testCompileClasspath
|
||||
com.google.cloud:google-cloud-pubsub:1.150.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-secretmanager:2.51.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-secretmanager:2.94.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
|
||||
com.google.cloud:google-cloud-spanner:6.117.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-storage-control:2.49.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-storage:2.68.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-storage:2.70.0=testCompileClasspath
|
||||
com.google.cloud:google-cloud-tasks:2.51.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.cloud:google-cloud-tasks:2.94.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
|
||||
com.google.cloud:grpc-gcp:1.10.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.cloud:libraries-bom:26.48.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.37.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,checkstyle,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.code.gson:gson:2.10.1=soy
|
||||
com.google.code.gson:gson:2.13.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.common.html.types:types:1.0.8=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.dagger:dagger-compiler:2.59.2=annotationProcessor,testAnnotationProcessor
|
||||
com.google.dagger:dagger-spi:2.59.2=annotationProcessor,testAnnotationProcessor
|
||||
com.google.dagger:dagger:2.59.2=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.devtools.ksp:symbol-processing-api:2.2.20-2.0.3=annotationProcessor,testAnnotationProcessor
|
||||
com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.42.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,checkstyle,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.code.gson:gson:2.14.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.dagger:dagger-compiler:2.60.1=annotationProcessor,testAnnotationProcessor
|
||||
com.google.dagger:dagger-spi:2.60.1=annotationProcessor,testAnnotationProcessor
|
||||
com.google.dagger:dagger:2.60.1=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.devtools.ksp:symbol-processing-api:2.3.7=annotationProcessor,testAnnotationProcessor
|
||||
com.google.errorprone:error_prone_annotation:2.48.0=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.errorprone:error_prone_annotations:2.20.0=soy
|
||||
com.google.errorprone:error_prone_annotations:2.36.0=checkstyle
|
||||
com.google.errorprone:error_prone_annotations:2.48.0=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.errorprone:error_prone_annotations:2.49.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.errorprone:error_prone_check_api:2.48.0=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.errorprone:error_prone_core:2.48.0=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.escapevelocity:escapevelocity:1.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.flatbuffers:flatbuffers-java:24.3.25=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.flogger:flogger-system-backend:0.7.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.flogger:flogger:0.7.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.flogger:google-extensions:0.7.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.flogger:flogger-system-backend:0.7.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.flogger:flogger:0.7.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.flogger:google-extensions:0.7.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.googlejavaformat:google-java-format:1.34.1=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.guava:failureaccess:1.0.1=soy
|
||||
com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,compileClasspath,deploy_jar,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.guava:guava-parent:32.1.1-jre=soy
|
||||
com.google.guava:guava-testlib:33.3.0-jre=testRuntimeClasspath
|
||||
com.google.guava:guava-testlib:33.6.0-jre=testCompileClasspath
|
||||
com.google.guava:guava:32.1.1-jre=soy
|
||||
com.google.guava:guava:33.4.8-jre=checkstyle
|
||||
com.google.guava:guava:33.5.0-jre=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.guava:guava:33.6.0-jre=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.guava:guava:33.5.0-jre=nonprodAnnotationProcessor
|
||||
com.google.guava:guava:33.6.0-jre=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,compileClasspath,deploy_jar,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.gwt:gwt-user:2.10.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.http-client:google-http-client-apache-v2:2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
com.google.http-client:google-http-client-apache-v2:2.1.0=testCompileClasspath,testRuntimeClasspath
|
||||
com.google.http-client:google-http-client-appengine:1.46.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
com.google.http-client:google-http-client-appengine:2.1.0=testCompileClasspath,testRuntimeClasspath
|
||||
com.google.http-client:google-http-client-gson:2.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.http-client:google-http-client-jackson2:1.46.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
com.google.http-client:google-http-client-jackson2:2.1.0=testCompileClasspath,testRuntimeClasspath
|
||||
com.google.http-client:google-http-client-apache-v2:2.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
com.google.http-client:google-http-client-apache-v2:2.1.1=testCompileClasspath,testRuntimeClasspath
|
||||
com.google.http-client:google-http-client-appengine:2.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.http-client:google-http-client-appengine:2.1.1=testCompileClasspath
|
||||
com.google.http-client:google-http-client-gson:2.1.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.http-client:google-http-client-jackson2:2.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.http-client:google-http-client-jackson2:2.1.1=testCompileClasspath
|
||||
com.google.http-client:google-http-client-protobuf:2.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.http-client:google-http-client:2.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.inject:guice:7.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.http-client:google-http-client:2.1.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.j2objc:j2objc-annotations:3.0.0=checkstyle
|
||||
com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,compileClasspath,deploy_jar,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.jsinterop:jsinterop-annotations:1.0.1=soy
|
||||
com.google.jsinterop:jsinterop-annotations:2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.monitoring-client:contrib:1.0.7=testCompileClasspath,testRuntimeClasspath
|
||||
com.google.monitoring-client:metrics:1.0.7=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.monitoring-client:stackdriver:1.0.7=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
@@ -194,25 +192,21 @@ com.google.oauth-client:google-oauth-client-jetty:1.39.0=compileClasspath,nonpro
|
||||
com.google.oauth-client:google-oauth-client-servlet:1.36.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.oauth-client:google-oauth-client-servlet:1.39.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
|
||||
com.google.oauth-client:google-oauth-client:1.39.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.protobuf:protobuf-java-util:4.33.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
com.google.protobuf:protobuf-java-util:4.35.0-RC2=testCompileClasspath,testRuntimeClasspath
|
||||
com.google.protobuf:protobuf-java:3.21.7=soy
|
||||
com.google.protobuf:protobuf-java-util:4.33.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.protobuf:protobuf-java:4.35.0-RC2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.protobuf:protobuf-java:4.35.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.re2j:re2j:1.8=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.template:soy:2024-02-26=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.truth:truth:1.4.5=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.googlecode.json-simple:json-simple:1.1.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.ibm.icu:icu4j:73.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
|
||||
com.jcraft:jsch:0.1.55=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.ibm.icu:icu4j:73.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.lmax:disruptor:3.4.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.puppycrawl.tools:checkstyle:10.24.0=checkstyle
|
||||
com.squareup.okhttp3:okhttp:4.12.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.squareup.okhttp3:okhttp-jvm:5.4.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.squareup.okhttp3:okhttp:5.4.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.squareup.okio:okio-bom:3.0.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.squareup.okio:okio-fakefilesystem-jvm:3.4.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.squareup.okio:okio-fakefilesystem:3.4.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.squareup.okio:okio-jvm:3.6.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.squareup.okio:okio:3.6.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.squareup.okio:okio-jvm:3.17.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.squareup.okio:okio:3.17.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.squareup.wire:wire-compiler:4.5.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.squareup.wire:wire-grpc-server-generator:4.5.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.squareup.wire:wire-grpc-server:4.5.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
@@ -229,17 +223,18 @@ com.squareup:kotlinpoet:1.11.0=annotationProcessor,testAnnotationProcessor
|
||||
com.squareup:kotlinpoet:1.15.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.sun.istack:istack-commons-runtime:4.1.2=deploy_jar,jaxb,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.sun.istack:istack-commons-tools:4.1.2=jaxb
|
||||
com.sun.xml.bind.external:relaxng-datatype:4.0.8=jaxb
|
||||
com.sun.xml.bind.external:rngom:4.0.8=jaxb
|
||||
com.sun.xml.bind.external:relaxng-datatype:4.0.9=jaxb
|
||||
com.sun.xml.bind.external:rngom:4.0.9=jaxb
|
||||
com.sun.xml.dtd-parser:dtd-parser:1.5.1=jaxb
|
||||
com.zaxxer:HikariCP:7.0.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.zaxxer:HikariCP:7.0.2=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.zaxxer:HikariCP:7.1.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
|
||||
commons-beanutils:commons-beanutils:1.10.1=checkstyle
|
||||
commons-codec:commons-codec:1.15=checkstyle
|
||||
commons-codec:commons-codec:1.19.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
commons-collections:commons-collections:3.2.2=checkstyle
|
||||
commons-io:commons-io:2.20.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
commons-logging:commons-logging:1.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
dnsjava:dnsjava:3.6.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
dnsjava:dnsjava:3.6.5=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
guru.nidi.com.eclipsesource.j2v8:j2v8_linux_x86_64:4.6.0=testRuntimeClasspath
|
||||
guru.nidi.com.eclipsesource.j2v8:j2v8_macosx_x86_64:4.6.0=testRuntimeClasspath
|
||||
guru.nidi.com.eclipsesource.j2v8:j2v8_win32_x86:4.6.0=testRuntimeClasspath
|
||||
@@ -252,95 +247,83 @@ io.apicurio:apicurio-registry-protobuf-schema-utilities:3.0.0.M2=compileClasspat
|
||||
io.github.classgraph:classgraph:4.8.162=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
|
||||
io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
|
||||
io.github.java-diff-utils:java-diff-utils:4.16=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.github.java-diff-utils:java-diff-utils:4.17=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.github.ss-bhatt:testcontainers-valkey:1.0.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-alts:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.grpc:grpc-alts:1.81.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-api:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.grpc:grpc-api:1.81.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-auth:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.grpc:grpc-auth:1.81.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-census:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-context:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.grpc:grpc-context:1.81.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-core:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.grpc:grpc-core:1.81.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-googleapis:1.76.2=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.grpc:grpc-googleapis:1.81.0=testRuntimeClasspath
|
||||
io.grpc:grpc-grpclb:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.grpc:grpc-grpclb:1.81.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-inprocess:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.grpc:grpc-inprocess:1.81.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-netty-shaded:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.grpc:grpc-netty-shaded:1.81.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-netty:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-opentelemetry:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.grpc:grpc-opentelemetry:1.81.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-protobuf-lite:1.76.2=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.grpc:grpc-protobuf-lite:1.81.0=testRuntimeClasspath
|
||||
io.grpc:grpc-protobuf:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.grpc:grpc-protobuf:1.81.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-rls:1.76.2=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.grpc:grpc-rls:1.81.0=testRuntimeClasspath
|
||||
io.grpc:grpc-services:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath
|
||||
io.grpc:grpc-services:1.81.0=testRuntimeClasspath
|
||||
io.grpc:grpc-stub:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.grpc:grpc-stub:1.81.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-util:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath
|
||||
io.grpc:grpc-util:1.81.0=testRuntimeClasspath
|
||||
io.grpc:grpc-xds:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath
|
||||
io.grpc:grpc-xds:1.81.0=testRuntimeClasspath
|
||||
io.netty:netty-buffer:4.1.124.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.netty:netty-codec-http2:4.1.124.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.netty:netty-codec-http:4.1.124.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.netty:netty-codec-socks:4.1.124.Final=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
io.netty:netty-codec:4.1.124.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.netty:netty-common:4.1.124.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.netty:netty-handler-proxy:4.1.124.Final=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
io.netty:netty-handler:4.1.124.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.netty:netty-resolver:4.1.124.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-alts:1.81.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-api:1.81.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-auth:1.81.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-census:1.81.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-context:1.81.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-core:1.81.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-googleapis:1.81.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-grpclb:1.81.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-inprocess:1.81.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-netty-shaded:1.81.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-netty:1.81.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-opentelemetry:1.81.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-protobuf-lite:1.81.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-protobuf:1.81.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-rls:1.81.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-services:1.81.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-stub:1.81.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-testing:1.70.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-util:1.81.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-xds:1.81.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.netty:netty-buffer:4.2.16.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.netty:netty-codec-base:4.2.16.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.netty:netty-codec-compression:4.2.16.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.netty:netty-codec-http2:4.1.132.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.netty:netty-codec-http:4.2.16.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.netty:netty-codec-marshalling:4.2.16.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.netty:netty-codec-protobuf:4.2.16.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.netty:netty-codec-socks:4.1.132.Final=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
io.netty:netty-codec:4.2.16.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.netty:netty-common:4.2.16.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.netty:netty-handler-proxy:4.1.132.Final=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
io.netty:netty-handler:4.2.16.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.netty:netty-resolver:4.2.16.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.netty:netty-tcnative-boringssl-static:2.0.52.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.netty:netty-tcnative-classes:2.0.52.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.netty:netty-transport-native-unix-common:4.1.124.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.netty:netty-transport:4.1.124.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.netty:netty-transport-native-unix-common:4.2.16.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.netty:netty-transport:4.2.16.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.opencensus:opencensus-api:0.31.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.opencensus:opencensus-contrib-exemplar-util:0.31.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.opencensus:opencensus-contrib-grpc-metrics:0.31.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
|
||||
io.opencensus:opencensus-contrib-grpc-metrics:0.31.1=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
io.opencensus:opencensus-contrib-exemplar-util:0.31.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.opencensus:opencensus-contrib-grpc-metrics:0.31.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.opencensus:opencensus-contrib-grpc-util:0.31.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.opencensus:opencensus-contrib-http-util:0.31.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.opencensus:opencensus-contrib-resource-util:0.31.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.opencensus:opencensus-exporter-metrics-util:0.31.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.opencensus:opencensus-exporter-stats-stackdriver:0.31.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.opencensus:opencensus-impl-core:0.31.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.opencensus:opencensus-impl:0.31.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry.contrib:opentelemetry-gcp-resources:1.37.0-alpha=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.opentelemetry.contrib:opentelemetry-gcp-resources:1.45.0-alpha=testCompileClasspath,testRuntimeClasspath
|
||||
io.opencensus:opencensus-contrib-resource-util:0.31.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.opencensus:opencensus-exporter-metrics-util:0.31.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.opencensus:opencensus-exporter-stats-stackdriver:0.31.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.opencensus:opencensus-impl-core:0.31.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.opencensus:opencensus-impl:0.31.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry.contrib:opentelemetry-gcp-resources:1.37.0-alpha=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
io.opentelemetry.contrib:opentelemetry-gcp-resources:1.45.0-alpha=testCompileClasspath
|
||||
io.opentelemetry.instrumentation:opentelemetry-grpc-1.6:2.1.0-alpha=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.1.0-alpha=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry:opentelemetry-api:1.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.opentelemetry:opentelemetry-api:1.62.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry:opentelemetry-api:1.57.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.opentelemetry:opentelemetry-api:1.64.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry:opentelemetry-bom:1.42.1=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
io.opentelemetry:opentelemetry-common:1.62.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry:opentelemetry-context:1.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.opentelemetry:opentelemetry-context:1.62.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry:opentelemetry-exporter-logging:1.62.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry:opentelemetry-common:1.57.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.opentelemetry:opentelemetry-common:1.64.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry:opentelemetry-context:1.57.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.opentelemetry:opentelemetry-context:1.64.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry:opentelemetry-exporter-logging:1.64.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry:opentelemetry-extension-incubator:1.35.0-alpha=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk-common:1.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk-common:1.62.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.47.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.62.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.62.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk-logs:1.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk-logs:1.62.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk-metrics:1.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk-metrics:1.62.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk-trace:1.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk-trace:1.62.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk:1.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk:1.62.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk-common:1.57.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk-common:1.64.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.57.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.64.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.64.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk-logs:1.57.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk-logs:1.64.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk-metrics:1.57.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk-metrics:1.64.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk-trace:1.57.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk-trace:1.64.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk:1.57.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.opentelemetry:opentelemetry-sdk:1.64.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.outfoxx:swiftpoet:1.3.1=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
io.perfmark:perfmark-api:0.27.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
io.protostuff:protostuff-api:1.8.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
@@ -351,27 +334,26 @@ jakarta-regexp:jakarta-regexp:1.4=compileClasspath,deploy_jar,nonprodCompileClas
|
||||
jakarta.activation:jakarta.activation-api:2.1.4=jaxb
|
||||
jakarta.activation:jakarta.activation-api:2.2.0-M1=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
jakarta.activation:jakarta.activation-api:2.2.0-M2=compileClasspath,nonprodCompileClasspath,testCompileClasspath
|
||||
jakarta.inject:jakarta.inject-api:2.0.1=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
|
||||
jakarta.inject:jakarta.inject-api:2.0.1=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
|
||||
jakarta.mail:jakarta.mail-api:2.2.0-M1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
jakarta.persistence:jakarta.persistence-api:3.2.0=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
|
||||
jakarta.servlet:jakarta.servlet-api:6.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
jakarta.servlet:jakarta.servlet-api:6.0.0=testCompileClasspath,testRuntimeClasspath
|
||||
jakarta.servlet:jakarta.servlet-api:6.2.0-M2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
jakarta.transaction:jakarta.transaction-api:2.0.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
jakarta.xml.bind:jakarta.xml.bind-api:4.0.4=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
jakarta.xml.bind:jakarta.xml.bind-api:4.0.5=jaxb
|
||||
jakarta.xml.bind:jakarta.xml.bind-api:4.1.0-M1=compileClasspath,nonprodCompileClasspath,testCompileClasspath
|
||||
javax.annotation:javax.annotation-api:1.3.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
javax.annotation:jsr250-api:1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
|
||||
javax.inject:javax.inject:1=annotationProcessor,compileClasspath,deploy_jar,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
|
||||
javax.inject:javax.inject:1=annotationProcessor,compileClasspath,deploy_jar,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
|
||||
javax.jdo:jdo2-api:2.3-20090302111651=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
javax.validation:validation-api:1.0.0.GA=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
joda-time:joda-time:2.14.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
junit:junit:4.13.2=nonprodCompileClasspath,nonprodRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
net.arnx:nashorn-promise:0.1.1=testRuntimeClasspath
|
||||
net.bytebuddy:byte-buddy-agent:1.17.7=testCompileClasspath,testRuntimeClasspath
|
||||
net.bytebuddy:byte-buddy:1.17.7=compileClasspath,nonprodCompileClasspath
|
||||
net.bytebuddy:byte-buddy:1.18.8=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
net.bytebuddy:byte-buddy:1.18.8-jdk5=testCompileClasspath
|
||||
net.java.dev.jna:jna:5.13.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
net.bytebuddy:byte-buddy:1.18.11=testCompileClasspath,testRuntimeClasspath
|
||||
net.bytebuddy:byte-buddy:1.18.8=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath
|
||||
net.java.dev.jna:jna:5.13.0=nonprodCompileClasspath,nonprodRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
net.ltgt.gradle.incap:incap:0.2=annotationProcessor,testAnnotationProcessor
|
||||
net.sf.saxon:Saxon-HE:12.5=checkstyle
|
||||
org.abego.treelayout:org.abego.treelayout.core:1.0.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
@@ -382,25 +364,25 @@ org.antlr:antlr4:4.13.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonp
|
||||
org.apache.arrow:arrow-format:17.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.arrow:arrow-memory-core:17.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.arrow:arrow-vector:17.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.avro:avro:1.11.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-model-fn-execution:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-model-job-management:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-model-pipeline:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.avro:avro:1.12.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-model-fn-execution:2.75.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-model-job-management:2.75.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-model-pipeline:2.75.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-runners-core-construction-java:2.54.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-runners-core-java:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-runners-direct-java:2.72.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-runners-google-cloud-dataflow-java:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-runners-java-fn-execution:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-sdks-java-core:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-sdks-java-expansion-service:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-sdks-java-extensions-arrow:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-sdks-java-extensions-avro:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-sdks-java-extensions-google-cloud-platform-core:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-sdks-java-extensions-protobuf:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-runners-core-java:2.75.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-runners-direct-java:2.75.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-runners-google-cloud-dataflow-java:2.75.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-runners-java-fn-execution:2.75.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-sdks-java-core:2.75.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-sdks-java-expansion-service:2.75.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-sdks-java-extensions-arrow:2.75.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-sdks-java-extensions-avro:2.75.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-sdks-java-extensions-google-cloud-platform-core:2.75.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-sdks-java-extensions-protobuf:2.75.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-sdks-java-fn-execution:2.54.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-sdks-java-harness:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-sdks-java-io-google-cloud-platform:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-sdks-java-transform-service-launcher:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-sdks-java-harness:2.75.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-sdks-java-io-google-cloud-platform:2.75.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-sdks-java-transform-service-launcher:2.75.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-vendor-grpc-1_60_1:0.1=testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-vendor-grpc-1_69_0:0.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.beam:beam-vendor-guava-32_1_2-jre:0.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
@@ -410,7 +392,7 @@ org.apache.commons:commons-exec:1.3=testRuntimeClasspath
|
||||
org.apache.commons:commons-lang3:3.18.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
org.apache.commons:commons-lang3:3.20.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.commons:commons-lang3:3.8.1=checkstyle
|
||||
org.apache.commons:commons-pool2:2.12.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.commons:commons-pool2:2.13.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.commons:commons-text:1.15.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.commons:commons-text:1.3=checkstyle
|
||||
org.apache.ftpserver:ftplet-api:1.2.1=testCompileClasspath,testRuntimeClasspath
|
||||
@@ -427,56 +409,55 @@ org.apache.maven.doxia:doxia-logging-api:1.12.0=checkstyle
|
||||
org.apache.maven.doxia:doxia-module-xdoc:1.12.0=checkstyle
|
||||
org.apache.maven.doxia:doxia-sink-api:1.12.0=checkstyle
|
||||
org.apache.mina:mina-core:2.2.4=testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.sshd:sshd-common:3.0.0-M3=testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.sshd:sshd-core:3.0.0-M3=testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.sshd:sshd-scp:3.0.0-M3=testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.sshd:sshd-sftp:3.0.0-M3=testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.tomcat:tomcat-annotations-api:11.0.22=testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.sshd:sshd-common:3.0.0-M5=testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.sshd:sshd-core:3.0.0-M5=testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.sshd:sshd-scp:3.0.0-M5=testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.sshd:sshd-sftp:3.0.0-M5=testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.tomcat:tomcat-annotations-api:11.0.24=testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.xbean:xbean-reflect:3.7=checkstyle
|
||||
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath
|
||||
org.bouncycastle:bcpg-jdk18on:1.84=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.bouncycastle:bcpkix-jdk18on:1.84=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.bouncycastle:bcprov-jdk18on:1.84=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.bouncycastle:bcutil-jdk18on:1.84=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.checkerframework:checker-compat-qual:2.5.3=annotationProcessor,compileClasspath,nonprodCompileClasspath,soy,testAnnotationProcessor,testCompileClasspath
|
||||
org.bouncycastle:bcpg-jdk18on:1.85=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.bouncycastle:bcpkix-jdk18on:1.85=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.bouncycastle:bcprov-jdk18on:1.85=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.bouncycastle:bcutil-jdk18on:1.85=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.checkerframework:checker-compat-qual:2.5.3=annotationProcessor,compileClasspath,nonprodCompileClasspath,testAnnotationProcessor,testCompileClasspath
|
||||
org.checkerframework:checker-compat-qual:2.5.6=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
org.checkerframework:checker-qual:3.19.0=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
|
||||
org.checkerframework:checker-qual:3.33.0=soy
|
||||
org.checkerframework:checker-qual:3.49.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.checkerframework:checker-qual:3.49.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
|
||||
org.checkerframework:checker-qual:3.49.3=checkstyle
|
||||
org.codehaus.mojo:animal-sniffer-annotations:1.24=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
org.codehaus.mojo:animal-sniffer-annotations:1.27=testCompileClasspath,testRuntimeClasspath
|
||||
org.checkerframework:checker-qual:3.55.1=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
org.codehaus.mojo:animal-sniffer-annotations:1.27=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.codehaus.plexus:plexus-classworlds:2.6.0=checkstyle
|
||||
org.codehaus.plexus:plexus-component-annotations:2.1.0=checkstyle
|
||||
org.codehaus.plexus:plexus-container-default:2.1.0=checkstyle
|
||||
org.codehaus.plexus:plexus-utils:3.3.0=checkstyle
|
||||
org.codehaus.woodstox:stax2-api:4.2.2=testCompileClasspath,testRuntimeClasspath
|
||||
org.codehaus.woodstox:stax2-api:4.2.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.conscrypt:conscrypt-openjdk-uber:2.5.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.angus:angus-activation:2.0.3=jaxb
|
||||
org.eclipse.angus:angus-activation:2.1.0-M1=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
org.eclipse.angus:jakarta.mail:2.1.0-M1=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty.ee10:jetty-ee10-servlet:12.1.9=testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty.ee10:jetty-ee10-webapp:12.1.9=testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty.ee:jetty-ee-webapp:12.1.9=testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-http:12.1.9=testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-io:12.1.9=testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-security:12.1.9=testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-server:12.1.9=testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-session:12.1.9=testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-util:12.1.9=testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-xml:12.1.9=testCompileClasspath,testRuntimeClasspath
|
||||
org.flywaydb:flyway-core:12.6.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.flywaydb:flyway-database-postgresql:12.6.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.glassfish.jaxb:codemodel:4.0.8=jaxb
|
||||
org.eclipse.jetty.ee10:jetty-ee10-servlet:12.1.11=testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty.ee10:jetty-ee10-webapp:12.1.11=testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty.ee:jetty-ee-webapp:12.1.11=testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-http:12.1.11=testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-io:12.1.11=testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-security:12.1.11=testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-server:12.1.11=testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-session:12.1.11=testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-util:12.1.11=testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-xml:12.1.11=testCompileClasspath,testRuntimeClasspath
|
||||
org.flywaydb:flyway-core:13.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.flywaydb:flyway-database-postgresql:13.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.freemarker:freemarker:2.3.34=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.glassfish.jaxb:codemodel:4.0.9=jaxb
|
||||
org.glassfish.jaxb:jaxb-core:4.0.6=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
org.glassfish.jaxb:jaxb-core:4.0.8=jaxb
|
||||
org.glassfish.jaxb:jaxb-core:4.0.9=jaxb
|
||||
org.glassfish.jaxb:jaxb-runtime:4.0.6=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
org.glassfish.jaxb:jaxb-runtime:4.0.8=jaxb
|
||||
org.glassfish.jaxb:jaxb-xjc:4.0.8=jaxb
|
||||
org.glassfish.jaxb:jaxb-runtime:4.0.9=jaxb
|
||||
org.glassfish.jaxb:jaxb-xjc:4.0.9=jaxb
|
||||
org.glassfish.jaxb:txw2:4.0.6=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
org.glassfish.jaxb:txw2:4.0.8=jaxb
|
||||
org.glassfish.jaxb:xsom:4.0.8=jaxb
|
||||
org.gwtproject:gwt-user:2.10.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.glassfish.jaxb:txw2:4.0.9=jaxb
|
||||
org.glassfish.jaxb:xsom:4.0.9=jaxb
|
||||
org.hamcrest:hamcrest-core:1.3=nonprodCompileClasspath,nonprodRuntimeClasspath
|
||||
org.hamcrest:hamcrest-core:3.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.hamcrest:hamcrest-library:3.0=testCompileClasspath,testRuntimeClasspath
|
||||
@@ -492,113 +473,109 @@ org.jacoco:org.jacoco.core:0.8.14=jacocoAnt
|
||||
org.jacoco:org.jacoco.report:0.8.14=jacocoAnt
|
||||
org.javassist:javassist:3.28.0-GA=checkstyle
|
||||
org.jboss.logging:jboss-logging:3.6.3.Final=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
org.jcommander:jcommander:2.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.jcommander:jcommander:3.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.jetbrains.kotlin:kotlin-bom:1.4.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
org.jetbrains.kotlin:kotlin-metadata-jvm:2.2.20=annotationProcessor,testAnnotationProcessor
|
||||
org.jetbrains.kotlin:kotlin-metadata-jvm:2.3.21=annotationProcessor,testAnnotationProcessor
|
||||
org.jetbrains.kotlin:kotlin-reflect:1.6.10=annotationProcessor,testAnnotationProcessor
|
||||
org.jetbrains.kotlin:kotlin-reflect:1.9.20=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
org.jetbrains.kotlin:kotlin-stdlib-common:1.9.20=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.jetbrains.kotlin:kotlin-stdlib-common:2.2.21=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0=annotationProcessor,testAnnotationProcessor
|
||||
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0=annotationProcessor,testAnnotationProcessor
|
||||
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.jetbrains.kotlin:kotlin-stdlib:1.9.20=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.jetbrains.kotlin:kotlin-stdlib:2.2.20=annotationProcessor,testAnnotationProcessor
|
||||
org.jetbrains.kotlin:kotlin-stdlib:2.2.21=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.jetbrains.kotlin:kotlin-stdlib:2.3.21=annotationProcessor,testAnnotationProcessor
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.4.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-datetime:0.4.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.0.1=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.1=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
org.jetbrains:annotations:13.0=annotationProcessor,testAnnotationProcessor
|
||||
org.jetbrains:annotations:17.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.jline:jline:3.30.5=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.jetbrains:annotations:13.0=annotationProcessor,compileClasspath,deploy_jar,runtimeClasspath,testAnnotationProcessor
|
||||
org.jetbrains:annotations:17.0.0=nonprodCompileClasspath,nonprodRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.jline:jline:4.3.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.joda:joda-money:2.0.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.json:json:20230618=soy
|
||||
org.json:json:20251224=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.json:json:20260522=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.jsoup:jsoup:1.22.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.jspecify:jspecify:1.0.0=annotationProcessor,checkstyle,compileClasspath,deploy_jar,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
|
||||
org.junit-pioneer:junit-pioneer:2.3.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-api:5.13.4=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-engine:5.13.4=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-migrationsupport:5.13.4=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-params:5.13.4=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-commons:1.14.4=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-engine:1.14.4=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-launcher:1.14.4=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-runner:1.13.3=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-suite-api:1.14.4=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-api:6.1.2=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-engine:6.1.2=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-migrationsupport:6.1.2=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-params:6.1.2=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-commons:6.1.2=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-engine:6.1.2=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-launcher:6.1.2=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-runner:1.14.4=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-suite-api:6.1.2=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-suite-commons:1.14.4=testRuntimeClasspath
|
||||
org.junit:junit-bom:5.14.4=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-suite-engine:6.1.2=testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-suite:6.1.2=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit:junit-bom:6.1.2=testCompileClasspath,testRuntimeClasspath
|
||||
org.mockito:mockito-core:5.23.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.mockito:mockito-junit-jupiter:5.23.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.objenesis:objenesis:3.3=testRuntimeClasspath
|
||||
org.ogce:xpp3:1.1.6=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.ow2.asm:asm-analysis:9.5=soy
|
||||
org.ow2.asm:asm-analysis:9.7.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.ow2.asm:asm-commons:9.5=soy
|
||||
org.ow2.asm:asm-commons:9.7.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.ow2.asm:asm-commons:9.9=jacocoAnt
|
||||
org.ow2.asm:asm-tree:9.5=soy
|
||||
org.ow2.asm:asm-tree:9.7.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.ow2.asm:asm-tree:9.9=jacocoAnt
|
||||
org.ow2.asm:asm-util:9.5=soy
|
||||
org.ow2.asm:asm-util:9.7.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.ow2.asm:asm:9.5=soy
|
||||
org.ow2.asm:asm:9.7.1=compileClasspath,nonprodCompileClasspath
|
||||
org.ow2.asm:asm:9.8=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.ow2.asm:asm:9.9=jacocoAnt
|
||||
org.pcollections:pcollections:4.0.1=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
|
||||
org.postgresql:postgresql:42.7.11=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.postgresql:postgresql:42.7.13=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.reflections:reflections:0.10.2=checkstyle
|
||||
org.rnorth.duct-tape:duct-tape:1.0.8=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-api:4.44.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-chrome-driver:4.44.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-chromium-driver:4.44.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-devtools-v146:4.44.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-devtools-v147:4.44.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-devtools-v148:4.44.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-edge-driver:4.44.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-firefox-driver:4.44.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-http:4.44.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-ie-driver:4.44.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-java:4.44.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-json:4.44.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-manager:4.44.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-os:4.44.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-remote-driver:4.44.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-safari-driver:4.44.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-support:4.44.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.rnorth.duct-tape:duct-tape:1.0.8=nonprodCompileClasspath,nonprodRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-api:4.46.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-chrome-driver:4.46.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-chromium-driver:4.46.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-devtools-latest:4.46.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-devtools-v148:4.46.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-devtools-v149:4.46.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-devtools-v150:4.46.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-edge-driver:4.46.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-firefox-driver:4.46.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-http:4.46.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-ie-driver:4.46.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-java:4.46.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-json:4.46.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-manager:4.46.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-os:4.46.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-remote-driver:4.46.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-safari-driver:4.46.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.seleniumhq.selenium:selenium-support:4.46.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.slf4j:jcl-over-slf4j:1.7.36=testCompileClasspath,testRuntimeClasspath
|
||||
org.slf4j:jul-to-slf4j:1.7.30=testRuntimeClasspath
|
||||
org.slf4j:slf4j-api:2.0.17=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.slf4j:slf4j-api:2.0.18=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.slf4j:slf4j-jdk14:2.0.17=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.snakeyaml:snakeyaml-engine:2.1=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
org.testcontainers:database-commons:1.21.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.testcontainers:jdbc:1.21.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.testcontainers:database-commons:1.21.4=nonprodCompileClasspath,nonprodRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.testcontainers:jdbc:1.21.4=nonprodCompileClasspath,nonprodRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.testcontainers:junit-jupiter:1.21.4=testCompileClasspath,testRuntimeClasspath
|
||||
org.testcontainers:postgresql:1.21.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.testcontainers:postgresql:1.21.4=nonprodCompileClasspath,nonprodRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.testcontainers:selenium:1.21.4=testCompileClasspath,testRuntimeClasspath
|
||||
org.testcontainers:testcontainers:1.21.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.testcontainers:testcontainers:1.21.4=nonprodCompileClasspath,nonprodRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.threeten:threetenbp:1.7.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.w3c.css:sac:1.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.webjars.npm:viz.js-graphviz-java:2.1.3=testRuntimeClasspath
|
||||
org.xerial.snappy:snappy-java:1.1.10.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.xmlresolver:xmlresolver:5.2.2=checkstyle
|
||||
org.yaml:snakeyaml:2.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.yaml:snakeyaml:2.5=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
redis.clients.authentication:redis-authx-core:0.1.1-beta2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
redis.clients:jedis:7.4.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
tools.jackson.core:jackson-core:3.1.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
tools.jackson.core:jackson-databind:3.1.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
tools.jackson:jackson-bom:3.1.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
redis.clients:jedis:8.0.0-beta1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
tools.jackson.core:jackson-core:3.1.5=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
tools.jackson.core:jackson-databind:3.1.5=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
tools.jackson:jackson-bom:3.1.5=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
us.fatehi:schemacrawler-api:17.1.7=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
us.fatehi:schemacrawler-diagram:17.11.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
us.fatehi:schemacrawler-operations:17.11.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
us.fatehi:schemacrawler-postgresql:17.11.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
us.fatehi:schemacrawler-text:17.11.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
us.fatehi:schemacrawler-diagram:17.12.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
us.fatehi:schemacrawler-operations:17.12.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
us.fatehi:schemacrawler-postgresql:17.12.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
us.fatehi:schemacrawler-text:17.12.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
us.fatehi:schemacrawler-tools:17.1.7=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
us.fatehi:schemacrawler-utility:17.1.7=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
us.fatehi:schemacrawler:17.11.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
us.fatehi:schemacrawler:17.12.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
xerces:xmlParserAPIs:2.6.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
empty=devtool,shadow
|
||||
|
||||
@@ -54,9 +54,9 @@ public class BatchModule {
|
||||
static final int DEFAULT_MAX_QPS = 10;
|
||||
|
||||
@Provides
|
||||
@Parameter("sender")
|
||||
static String provideSender(HttpServletRequest req) {
|
||||
return extractRequiredParameter(req, "sender");
|
||||
@Parameter("replyTo")
|
||||
static String provideReplyTo(HttpServletRequest req) {
|
||||
return extractRequiredParameter(req, "replyTo");
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -18,7 +18,9 @@ import static google.registry.request.Action.Method.GET;
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
|
||||
import com.google.api.services.gmail.Gmail;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.MediaType;
|
||||
import dagger.Lazy;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.groups.GmailClient;
|
||||
@@ -31,6 +33,7 @@ import google.registry.util.Retrier;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.mail.internet.AddressException;
|
||||
import jakarta.mail.internet.InternetAddress;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Action that executes a canned script specified by the caller.
|
||||
@@ -62,13 +65,16 @@ public class CannedScriptExecutionAction implements Runnable {
|
||||
@Inject Response response;
|
||||
|
||||
@Inject
|
||||
@Parameter("sender")
|
||||
String sender;
|
||||
@Parameter("replyTo")
|
||||
String replyTo;
|
||||
|
||||
@Inject
|
||||
@Parameter("receiver")
|
||||
String receiver;
|
||||
|
||||
@Config("invoiceReplyToEmailAddress")
|
||||
Optional<InternetAddress> replyToEmailAddressFromConfig;
|
||||
|
||||
@Inject
|
||||
CannedScriptExecutionAction() {}
|
||||
|
||||
@@ -77,20 +83,36 @@ public class CannedScriptExecutionAction implements Runnable {
|
||||
// For b/510340944, validating a new G Workspace user can send email. Code below can be
|
||||
// removed or changed afterward.
|
||||
try {
|
||||
logger.atInfo().log("Sending email from %s to %s", sender, receiver);
|
||||
logger.atInfo().log("Sending email to %s with replyTo %s", receiver, replyTo);
|
||||
GmailClient gmailClient =
|
||||
new GmailClient(
|
||||
gmail, retrier, isEmailSendingEnabled, sender, sender, new InternetAddress(sender));
|
||||
new GmailClient(gmail, retrier, isEmailSendingEnabled, new InternetAddress(replyTo));
|
||||
gmailClient.sendEmail(
|
||||
EmailMessage.newBuilder()
|
||||
.addRecipient(new InternetAddress(receiver))
|
||||
.setSubject(String.format("Email send test from %s", sender))
|
||||
.setBody(String.format("This is a test email sent from %s to %s.", sender, receiver))
|
||||
.setSubject(String.format("Email with manually set replyTo header %s", replyTo))
|
||||
.setBody("See subject")
|
||||
.build());
|
||||
response.setPayload("Email sent successfully.");
|
||||
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
gmailClient.sendEmail(
|
||||
EmailMessage.newBuilder()
|
||||
.setSubject(
|
||||
String.format(
|
||||
"Email with injected replyTo header %s", replyToEmailAddressFromConfig))
|
||||
.setBody("See header")
|
||||
.setRecipients(ImmutableList.of(new InternetAddress(receiver)))
|
||||
.setReplyToEmailAddress(replyToEmailAddressFromConfig)
|
||||
.setContentType(MediaType.HTML_UTF_8)
|
||||
.build());
|
||||
response.setPayload("Emails sent successfully.");
|
||||
} catch (AddressException e) {
|
||||
logger.atWarning().withCause(e).log(
|
||||
"Invalid email address: sender=%s, receiver=%s", sender, receiver);
|
||||
"Invalid email address: sender=%s, receiver=%s", replyTo, receiver);
|
||||
response.setStatus(400);
|
||||
response.setPayload("Invalid email address provided.");
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -212,17 +212,16 @@ public class RdeIO {
|
||||
}
|
||||
}
|
||||
|
||||
// Don't write the IDN elements for BRDA.
|
||||
// Don't write the IDN elements or EPP params for BRDA.
|
||||
if (mode == RdeMode.FULL) {
|
||||
for (IdnTableEnum idn : IdnTableEnum.values()) {
|
||||
output.write(marshaller.marshalIdn(idn.getTable()));
|
||||
counter.increment(RdeResourceType.IDN);
|
||||
}
|
||||
output.write(marshaller.marshalRdeEppParams());
|
||||
counter.increment(RdeResourceType.EPP_PARAMS);
|
||||
}
|
||||
|
||||
output.write(marshaller.marshalRdeEppParams());
|
||||
counter.increment(RdeResourceType.EPP_PARAMS);
|
||||
|
||||
// Output XML that says how many resources were emitted.
|
||||
header = counter.makeHeader(tld, mode);
|
||||
output.write(marshaller.marshalOrDie(new XjcRdeHeaderElement(header)));
|
||||
|
||||
@@ -21,7 +21,6 @@ import static google.registry.beam.rde.RdePipeline.TupleTags.DOMAIN_FRAGMENTS;
|
||||
import static google.registry.beam.rde.RdePipeline.TupleTags.EXTERNAL_HOST_FRAGMENTS;
|
||||
import static google.registry.beam.rde.RdePipeline.TupleTags.HOST_TO_PENDING_DEPOSIT;
|
||||
import static google.registry.beam.rde.RdePipeline.TupleTags.PENDING_DEPOSIT;
|
||||
import static google.registry.beam.rde.RdePipeline.TupleTags.REFERENCED_CONTACTS;
|
||||
import static google.registry.beam.rde.RdePipeline.TupleTags.REFERENCED_HOSTS;
|
||||
import static google.registry.beam.rde.RdePipeline.TupleTags.REVISION_ID;
|
||||
import static google.registry.beam.rde.RdePipeline.TupleTags.SUPERORDINATE_DOMAINS;
|
||||
@@ -131,9 +130,8 @@ import org.apache.beam.sdk.values.TypeDescriptor;
|
||||
*
|
||||
* After the most recent (live) domain resources are loaded from the corresponding history objects,
|
||||
* we marshall them to deposit fragments and emit the (pending deposit: deposit fragment) pairs for
|
||||
* further processing. We also find all the contacts and hosts referenced by a given domain and emit
|
||||
* pairs of (contact/host repo ID: pending deposit) for all RDE pending deposits for further
|
||||
* processing.
|
||||
* further processing. We also find all the hosts referenced by a given domain and emit pairs of
|
||||
* (host repo ID: pending deposit) for all RDE pending deposits for further processing.
|
||||
*
|
||||
* <h3>{@link Host}</h3>
|
||||
*
|
||||
@@ -358,7 +356,6 @@ public class RdePipeline implements Serializable {
|
||||
|
||||
private <T extends HistoryEntry> EppResource loadResourceByHistoryEntryId(
|
||||
Class<T> historyEntryClazz, String repoId, long revisionId) {
|
||||
|
||||
return tm().transact(
|
||||
() ->
|
||||
tm().loadByKey(
|
||||
@@ -372,10 +369,10 @@ public class RdePipeline implements Serializable {
|
||||
* Remove unreferenced resources by joining the (repoId, pendingDeposit) pair with the (repoId,
|
||||
* revisionId) on the repoId.
|
||||
*
|
||||
* <p>The (repoId, pendingDeposit) pairs denote resources (contact, host) that are referenced from
|
||||
* a domain, that are to be included in the corresponding pending deposit.
|
||||
* <p>The (repoId, pendingDeposit) pairs denote hosts that are referenced from a domain, that are
|
||||
* to be included in the corresponding pending deposit.
|
||||
*
|
||||
* <p>The (repoId, revisionId) paris come from the most recent history entry query, which can be
|
||||
* <p>The (repoId, revisionId) pairs come from the most recent history entry query, which can be
|
||||
* used to load the embedded resources themselves.
|
||||
*
|
||||
* @return a pair of (repoId, ([pendingDeposit], [revisionId])) where neither the pendingDeposit
|
||||
@@ -423,7 +420,7 @@ public class RdePipeline implements Serializable {
|
||||
Counter domainFragmentCounter = Metrics.counter("RDE", "DomainFragment");
|
||||
Counter referencedHostCounter = Metrics.counter("RDE", "ReferencedHost");
|
||||
return domainHistories.apply(
|
||||
"Map DomainHistory to DepositFragment " + "and emit referenced Contact and Host",
|
||||
"Map DomainHistory to DepositFragment and emit referenced Host",
|
||||
ParDo.of(
|
||||
new DoFn<KV<String, Long>, KV<PendingDeposit, DepositFragment>>() {
|
||||
@ProcessElement
|
||||
@@ -465,8 +462,7 @@ public class RdePipeline implements Serializable {
|
||||
});
|
||||
}
|
||||
})
|
||||
.withOutputTags(
|
||||
DOMAIN_FRAGMENTS, TupleTagList.of(REFERENCED_CONTACTS).and(REFERENCED_HOSTS)));
|
||||
.withOutputTags(DOMAIN_FRAGMENTS, TupleTagList.of(REFERENCED_HOSTS)));
|
||||
}
|
||||
|
||||
private PCollectionTuple processHostHistories(
|
||||
@@ -627,9 +623,6 @@ public class RdePipeline implements Serializable {
|
||||
protected static final TupleTag<KV<PendingDeposit, DepositFragment>> DOMAIN_FRAGMENTS =
|
||||
new TupleTag<>() {};
|
||||
|
||||
protected static final TupleTag<KV<String, PendingDeposit>> REFERENCED_CONTACTS =
|
||||
new TupleTag<>() {};
|
||||
|
||||
protected static final TupleTag<KV<String, PendingDeposit>> REFERENCED_HOSTS =
|
||||
new TupleTag<>() {};
|
||||
|
||||
|
||||
@@ -226,18 +226,17 @@ public class SafeBrowsingTransforms {
|
||||
private void processResponse(
|
||||
CloseableHttpResponse response,
|
||||
ImmutableSet.Builder<KV<DomainNameInfo, ThreatMatch>> resultBuilder)
|
||||
throws JSONException, IOException {
|
||||
throws IOException {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
if (statusCode != SC_OK) {
|
||||
logger.atWarning().log("Got unexpected status code %s from response.", statusCode);
|
||||
} else {
|
||||
// Unpack the response body
|
||||
JSONObject responseBody =
|
||||
new JSONObject(
|
||||
CharStreams.toString(
|
||||
new InputStreamReader(response.getEntity().getContent(), UTF_8)));
|
||||
logger.atInfo().log("Got response: %s", responseBody);
|
||||
if (responseBody.length() == 0) {
|
||||
throw new IOException(
|
||||
String.format("Got unexpected status code %s from response.", statusCode));
|
||||
}
|
||||
// Unpack the response body
|
||||
try (InputStreamReader reader =
|
||||
new InputStreamReader(response.getEntity().getContent(), UTF_8)) {
|
||||
JSONObject responseBody = new JSONObject(CharStreams.toString(reader));
|
||||
if (responseBody.isEmpty()) {
|
||||
logger.atInfo().log("Response was empty, no threats detected.");
|
||||
} else {
|
||||
// Emit all DomainNameInfos with their API results.
|
||||
|
||||
@@ -68,6 +68,8 @@ public class BsaCredential {
|
||||
|
||||
private final Keyring keyring;
|
||||
|
||||
private final Gson gson;
|
||||
|
||||
private final Clock clock;
|
||||
|
||||
@Nullable private String authToken;
|
||||
@@ -79,11 +81,13 @@ public class BsaCredential {
|
||||
@Config("bsaAuthUrl") String authUrl,
|
||||
@Config("bsaAuthTokenExpiry") Duration authTokenExpiry,
|
||||
Keyring keyring,
|
||||
Gson gson,
|
||||
Clock clock) {
|
||||
this.urlConnectionService = urlConnectionService;
|
||||
this.authUrl = authUrl;
|
||||
this.authTokenExpiry = authTokenExpiry;
|
||||
this.keyring = keyring;
|
||||
this.gson = gson;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@@ -143,8 +147,7 @@ public class BsaCredential {
|
||||
// TODO: catch json syntax exception
|
||||
@SuppressWarnings("unchecked")
|
||||
String idToken =
|
||||
new Gson()
|
||||
.fromJson(new String(getResponseBytes(connection), UTF_8), Map.class)
|
||||
gson.fromJson(new String(getResponseBytes(connection), UTF_8), Map.class)
|
||||
.getOrDefault(ID_TOKEN, "")
|
||||
.toString();
|
||||
if (idToken.isEmpty()) {
|
||||
|
||||
+15
-11
@@ -38,6 +38,7 @@ import java.security.GeneralSecurityException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
@@ -60,12 +61,15 @@ public final class CacheModule {
|
||||
public static Optional<UnifiedJedis> provideJedis(
|
||||
@ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle,
|
||||
@Config("valkeyHostsAndPorts") Optional<ImmutableList<String>> valkeyHostsAndPorts,
|
||||
@Config("valkeySslSocketFactory") SSLSocketFactory valkeySslSocketFactory) {
|
||||
if (valkeyHostsAndPorts.map(ImmutableList::isEmpty).orElse(true)) {
|
||||
@Config("valkeyCertificateAuthority") Optional<String> valkeyCertificateAuthority) {
|
||||
if (valkeyHostsAndPorts.map(ImmutableList::isEmpty).orElse(true)
|
||||
|| valkeyCertificateAuthority.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
ImmutableSet<HostAndPort> hostsAndPorts =
|
||||
valkeyHostsAndPorts.get().stream().map(HostAndPort::from).collect(toImmutableSet());
|
||||
SSLSocketFactory valkeySslSocketFactory =
|
||||
createValkeySslSocketFactory(valkeyCertificateAuthority.get());
|
||||
JedisClientConfig clientConfig =
|
||||
DefaultJedisClientConfig.builder()
|
||||
.ssl(true)
|
||||
@@ -103,19 +107,19 @@ public final class CacheModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
public static HostCache provideHostCache(
|
||||
Optional<SimplifiedJedisClient> jedisClient, CacheMetrics cacheMetrics) {
|
||||
Optional<SimplifiedJedisClient> jedisClient, Clock clock, CacheMetrics cacheMetrics) {
|
||||
if (jedisClient.isEmpty()) {
|
||||
return repoId ->
|
||||
Optional.ofNullable(EppResource.loadByCache(VKey.create(Host.class, repoId)));
|
||||
return repoId -> {
|
||||
Instant now = clock.now();
|
||||
return Optional.ofNullable(EppResource.loadByCache(VKey.create(Host.class, repoId)))
|
||||
.filter(host -> now.isBefore(host.getDeletionTime()))
|
||||
.map(host -> (Host) host.cloneProjectedAtTime(now));
|
||||
};
|
||||
}
|
||||
return new MultilayerHostCache(jedisClient.get(), cacheMetrics);
|
||||
return new MultilayerHostCache(jedisClient.get(), clock, cacheMetrics);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Config("valkeySslSocketFactory")
|
||||
static SSLSocketFactory provideValkeySslSocketFactory(
|
||||
@Config("valkeyCertificateAuthority") String valkeyCertificateAuthority) {
|
||||
private static SSLSocketFactory createValkeySslSocketFactory(String valkeyCertificateAuthority) {
|
||||
try {
|
||||
ImmutableList<X509Certificate> trustedCerts =
|
||||
CertificateFactory.getInstance("X.509")
|
||||
|
||||
@@ -19,7 +19,6 @@ import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.util.Clock;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -30,12 +29,9 @@ import java.util.Optional;
|
||||
public class MultilayerDomainCache extends MultilayerEppResourceCache<Domain>
|
||||
implements DomainCache {
|
||||
|
||||
private final Clock clock;
|
||||
|
||||
public MultilayerDomainCache(
|
||||
SimplifiedJedisClient jedisClient, Clock clock, CacheMetrics cacheMetrics) {
|
||||
super(jedisClient, cacheMetrics);
|
||||
this.clock = clock;
|
||||
super(jedisClient, clock, cacheMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -46,15 +42,10 @@ public class MultilayerDomainCache extends MultilayerEppResourceCache<Domain>
|
||||
@Override
|
||||
protected Optional<Domain> loadFromDatabase(String domainName) {
|
||||
// Don't use the cache (avoid caching the same domain twice). Do use the replica SQL instance.
|
||||
Optional<Domain> possibleDomain =
|
||||
Optional.ofNullable(
|
||||
ForeignKeyUtils.loadMostRecentResourceObjects(
|
||||
Domain.class, ImmutableList.of(domainName), true)
|
||||
.get(domainName));
|
||||
Instant now = clock.now();
|
||||
return possibleDomain
|
||||
.filter(domain -> now.isBefore(domain.getDeletionTime()))
|
||||
.map(domain -> domain.cloneProjectedAtTime(now));
|
||||
return Optional.ofNullable(
|
||||
ForeignKeyUtils.loadMostRecentResourceObjects(
|
||||
Domain.class, ImmutableList.of(domainName), true)
|
||||
.get(domainName));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,7 +18,9 @@ import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import google.registry.config.RegistryConfig;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.util.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -36,11 +38,13 @@ public abstract class MultilayerEppResourceCache<V extends EppResource> {
|
||||
.build();
|
||||
|
||||
private final SimplifiedJedisClient jedisClient;
|
||||
private final Clock clock;
|
||||
private final CacheMetrics cacheMetrics;
|
||||
|
||||
protected MultilayerEppResourceCache(
|
||||
SimplifiedJedisClient jedisClient, CacheMetrics cacheMetrics) {
|
||||
SimplifiedJedisClient jedisClient, Clock clock, CacheMetrics cacheMetrics) {
|
||||
this.jedisClient = jedisClient;
|
||||
this.clock = clock;
|
||||
this.cacheMetrics = cacheMetrics;
|
||||
}
|
||||
|
||||
@@ -50,7 +54,16 @@ public abstract class MultilayerEppResourceCache<V extends EppResource> {
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Optional<V> loadFromCaches(Class<V> clazz, String key) {
|
||||
Instant now = clock.now();
|
||||
return (Optional<V>)
|
||||
loadFromCachesInternal(clazz, key)
|
||||
.filter(v -> now.isBefore(v.getDeletionTime()))
|
||||
.map(v -> v.cloneProjectedAtTime(now));
|
||||
}
|
||||
|
||||
private Optional<V> loadFromCachesInternal(Class<V> clazz, String key) {
|
||||
// hopefully the resource is in the local cache
|
||||
Optional<V> possibleValue = Optional.ofNullable(localCache.getIfPresent(key));
|
||||
if (possibleValue.isPresent()) {
|
||||
|
||||
@@ -18,6 +18,7 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
|
||||
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.util.Clock;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -27,8 +28,9 @@ import java.util.Optional;
|
||||
*/
|
||||
public class MultilayerHostCache extends MultilayerEppResourceCache<Host> implements HostCache {
|
||||
|
||||
public MultilayerHostCache(SimplifiedJedisClient jedisClient, CacheMetrics cacheMetrics) {
|
||||
super(jedisClient, cacheMetrics);
|
||||
public MultilayerHostCache(
|
||||
SimplifiedJedisClient jedisClient, Clock clock, CacheMetrics cacheMetrics) {
|
||||
super(jedisClient, clock, cacheMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,10 +25,20 @@ import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
import io.protostuff.Input;
|
||||
import io.protostuff.LinkedBuffer;
|
||||
import io.protostuff.Output;
|
||||
import io.protostuff.Pipe;
|
||||
import io.protostuff.ProtostuffIOUtil;
|
||||
import io.protostuff.Schema;
|
||||
import io.protostuff.WireFormat;
|
||||
import io.protostuff.runtime.DefaultIdStrategy;
|
||||
import io.protostuff.runtime.Delegate;
|
||||
import io.protostuff.runtime.RuntimeSchema;
|
||||
import java.io.IOException;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Optional;
|
||||
import redis.clients.jedis.AbstractPipeline;
|
||||
@@ -52,11 +62,20 @@ public class SimplifiedJedisClient {
|
||||
Domain.class, "d_",
|
||||
Host.class, "h_");
|
||||
|
||||
/** We need to inform Protostuff of the custom {@link InetAddress} delegates. */
|
||||
private static DefaultIdStrategy createIdStrategy() {
|
||||
DefaultIdStrategy strategy = new DefaultIdStrategy();
|
||||
strategy.registerDelegate(new GenericInetAddressDelegate<>(InetAddress.class));
|
||||
strategy.registerDelegate(new GenericInetAddressDelegate<>(Inet4Address.class));
|
||||
strategy.registerDelegate(new GenericInetAddressDelegate<>(Inet6Address.class));
|
||||
return strategy;
|
||||
}
|
||||
|
||||
private static final ImmutableMap<Class<? extends EppResource>, Schema<? extends EppResource>>
|
||||
VALUE_SCHEMAS =
|
||||
ImmutableMap.of(
|
||||
Domain.class, RuntimeSchema.getSchema(Domain.class),
|
||||
Host.class, RuntimeSchema.getSchema(Host.class));
|
||||
Host.class, RuntimeSchema.getSchema(Host.class, createIdStrategy()));
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
@@ -151,4 +170,46 @@ public class SimplifiedJedisClient {
|
||||
checkArgument(VALUE_SCHEMAS.containsKey(clazz), "Unknown class type %s", clazz);
|
||||
return (Schema<V>) VALUE_SCHEMAS.get(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom Protostuff {@link Delegate} for {@link InetAddress} and its subclasses.
|
||||
*
|
||||
* <p>This is required in Java 17+ because Protostuff's default runtime schema serialization
|
||||
* relies on reflection. Since {@link InetAddress} is part of the encapsulated {@code java.base}
|
||||
* module, reflective access is restricted and throws {@link
|
||||
* java.lang.reflect.InaccessibleObjectException}.
|
||||
*
|
||||
* <p>This delegate serializes the IP address as a raw byte array using {@link
|
||||
* InetAddress#getAddress()} and reconstructs it using {@link InetAddress#getByAddress(byte[])}
|
||||
*/
|
||||
private record GenericInetAddressDelegate<T extends InetAddress>(Class<T> clazz)
|
||||
implements Delegate<T> {
|
||||
|
||||
@Override
|
||||
public WireFormat.FieldType getFieldType() {
|
||||
return WireFormat.FieldType.BYTES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> typeClass() {
|
||||
return clazz;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public T readFrom(Input input) throws IOException {
|
||||
return (T) InetAddress.getByAddress(input.readByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(Output output, int number, T value, boolean repeated) throws IOException {
|
||||
output.writeByteArray(number, value.getAddress(), repeated);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transfer(Pipe pipe, Input input, Output output, int number, boolean repeated)
|
||||
throws IOException {
|
||||
output.writeByteArray(number, input.readByteArray(), repeated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ package google.registry.config;
|
||||
|
||||
import static com.google.common.base.Suppliers.memoize;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||
import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap;
|
||||
import static google.registry.config.ConfigUtils.makeUrl;
|
||||
import static google.registry.util.DateTimeUtils.START_INSTANT;
|
||||
@@ -38,6 +39,7 @@ import google.registry.dns.ReadDnsRefreshRequestsAction;
|
||||
import google.registry.model.common.DnsRefreshRequest;
|
||||
import google.registry.mosapi.MosApiClient;
|
||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||
import google.registry.rde.RdeUploadUrl;
|
||||
import google.registry.request.Action.Service;
|
||||
import google.registry.util.RegistryEnvironment;
|
||||
import google.registry.util.YamlUtils;
|
||||
@@ -48,6 +50,7 @@ import jakarta.mail.internet.AddressException;
|
||||
import jakarta.mail.internet.InternetAddress;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.time.DayOfWeek;
|
||||
@@ -58,6 +61,7 @@ import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
|
||||
/**
|
||||
* Central clearing-house for all configuration.
|
||||
@@ -173,6 +177,18 @@ public final class RegistryConfig {
|
||||
return config.registrarConsole.supportEmailAddress;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("consoleHistoryQueryLimit")
|
||||
public static int provideConsoleHistoryQueryLimit(RegistryConfigSettings config) {
|
||||
return config.registrarConsole.historyQueryLimit;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("consoleBulkDomainActionLimit")
|
||||
public static int provideConsoleBulkDomainActionLimit(RegistryConfigSettings config) {
|
||||
return config.registrarConsole.bulkDomainActionLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* The DUM file name, used as a file name base for DUM csv file
|
||||
*
|
||||
@@ -830,8 +846,8 @@ public final class RegistryConfig {
|
||||
*/
|
||||
@Provides
|
||||
@Config("rdeUploadUrl")
|
||||
public static URI provideRdeUploadUrl(RegistryConfigSettings config) {
|
||||
return URI.create(config.rde.uploadUrl);
|
||||
public static RdeUploadUrl provideRdeUploadUrl(RegistryConfigSettings config) {
|
||||
return RdeUploadUrl.create(URI.create(config.rde.uploadUrl));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1097,6 +1113,40 @@ public final class RegistryConfig {
|
||||
return config.registryPolicy.registryAdminClientId;
|
||||
}
|
||||
|
||||
/** Returns the total length of the Expiry Access Period. */
|
||||
@Provides
|
||||
@Config("domainExpiryAccessPeriodTotalLength")
|
||||
public static Duration provideDomainExpiryAccessPeriodTotalLength(
|
||||
RegistryConfigSettings config) {
|
||||
return Duration.ofSeconds(config.registryPolicy.domainExpiryAccessPeriod.totalLengthSeconds);
|
||||
}
|
||||
|
||||
/** Returns the length of each tier of the Expiry Access Period. */
|
||||
@Provides
|
||||
@Config("domainExpiryAccessPeriodTierLength")
|
||||
public static Duration provideDomainExpiryAccessPeriodTierLength(
|
||||
RegistryConfigSettings config) {
|
||||
return Duration.ofSeconds(config.registryPolicy.domainExpiryAccessPeriod.tierLengthSeconds);
|
||||
}
|
||||
|
||||
/** Returns a map of the fee for the first tier of the Expiry Access Period, by currency. */
|
||||
@Provides
|
||||
@Config("domainExpiryAccessPeriodInitialFee")
|
||||
public static ImmutableMap<CurrencyUnit, BigDecimal> provideDomainExpiryAccessPeriodInitialFee(
|
||||
RegistryConfigSettings config) {
|
||||
return config.registryPolicy.domainExpiryAccessPeriod.initialFee.entrySet().stream()
|
||||
.collect(toImmutableMap(entry -> CurrencyUnit.of(entry.getKey()), Entry::getValue));
|
||||
}
|
||||
|
||||
/** Returns a map of the fee for the last tier of the Expiry Access Period, by currency. */
|
||||
@Provides
|
||||
@Config("domainExpiryAccessPeriodFinalFee")
|
||||
public static ImmutableMap<CurrencyUnit, BigDecimal> provideDomainExpiryAccessPeriodFinalFee(
|
||||
RegistryConfigSettings config) {
|
||||
return config.registryPolicy.domainExpiryAccessPeriod.finalFee.entrySet().stream()
|
||||
.collect(toImmutableMap(entry -> CurrencyUnit.of(entry.getKey()), Entry::getValue));
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
static RegistryConfigSettings provideRegistryConfigSettings() {
|
||||
@@ -1471,6 +1521,103 @@ public final class RegistryConfig {
|
||||
.map(valkey -> ImmutableList.copyOf(valkey.hostsAndPorts));
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("eppServerPort")
|
||||
public static int provideEppServerPort(RegistryConfigSettings config) {
|
||||
return config.eppServer.port;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("eppServerHealthCheckPort")
|
||||
public static int provideEppServerHealthCheckPort(RegistryConfigSettings config) {
|
||||
return config.eppServer.healthCheckPort;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("eppServerSslPemBucket")
|
||||
public static String provideEppServerSslPemBucket(RegistryConfigSettings config) {
|
||||
return config.eppServer.sslPemBucket;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("eppServerSslPemFilename")
|
||||
public static String provideEppServerSslPemFilename(RegistryConfigSettings config) {
|
||||
return config.eppServer.sslPemFilename;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("eppServerKmsLocation")
|
||||
public static String provideEppServerKmsLocation(RegistryConfigSettings config) {
|
||||
return config.eppServer.kmsLocation;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("eppServerKmsKeyRing")
|
||||
public static String provideEppServerKmsKeyRing(RegistryConfigSettings config) {
|
||||
return config.eppServer.kmsKeyRing;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("eppServerKmsCryptoKey")
|
||||
public static String provideEppServerKmsCryptoKey(RegistryConfigSettings config) {
|
||||
return config.eppServer.kmsCryptoKey;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("eppServerMaxMessageLengthBytes")
|
||||
public static int provideEppServerMaxMessageLengthBytes(RegistryConfigSettings config) {
|
||||
return config.eppServer.maxMessageLengthBytes;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("eppServerHeaderLengthBytes")
|
||||
public static int provideEppServerHeaderLengthBytes(RegistryConfigSettings config) {
|
||||
return config.eppServer.headerLengthBytes;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("eppServerReadTimeoutSeconds")
|
||||
public static int provideEppServerReadTimeoutSeconds(RegistryConfigSettings config) {
|
||||
return config.eppServer.readTimeoutSeconds;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("eppServerMaxConnectionsPerIp")
|
||||
public static int provideEppServerMaxConnectionsPerIp(RegistryConfigSettings config) {
|
||||
return config.eppServer.maxConnectionsPerIp;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("eppServerMaxConnectionsPerCert")
|
||||
public static int provideEppServerMaxConnectionsPerCert(RegistryConfigSettings config) {
|
||||
return config.eppServer.maxConnectionsPerCert;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("eppServerCertificateCacheSeconds")
|
||||
public static int provideEppServerCertificateCacheSeconds(RegistryConfigSettings config) {
|
||||
return config.eppServer.serverCertificateCacheSeconds;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("eppServerQuota")
|
||||
public static RegistryConfigSettings.Quota provideEppServerQuota(
|
||||
RegistryConfigSettings config) {
|
||||
return config.eppServer.quota;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("eppServerFrontendMetricsRatio")
|
||||
public static double provideEppServerFrontendMetricsRatio(RegistryConfigSettings config) {
|
||||
return config.monitoring.frontendMetricsRatio;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("writeIntervalSeconds")
|
||||
public static int provideWriteIntervalSeconds(RegistryConfigSettings config) {
|
||||
return config.monitoring.writeIntervalSeconds;
|
||||
}
|
||||
|
||||
private static String formatComments(String text) {
|
||||
return Splitter.on('\n').omitEmptyStrings().trimResults().splitToList(text).stream()
|
||||
.map(s -> "# " + s)
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package google.registry.config;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -44,6 +45,7 @@ public class RegistryConfigSettings {
|
||||
public Bsa bsa;
|
||||
public MosApi mosapi;
|
||||
public Valkey valkey;
|
||||
public EppServer eppServer;
|
||||
|
||||
/** Configuration options that apply to the entire GCP project. */
|
||||
public static class GcpProject {
|
||||
@@ -94,6 +96,7 @@ public class RegistryConfigSettings {
|
||||
public String tmchCrlUrl;
|
||||
public String tmchMarksDbUrl;
|
||||
public String registryAdminClientId;
|
||||
public DomainExpiryAccessPeriod domainExpiryAccessPeriod;
|
||||
public String premiumTermsExportDisclaimer;
|
||||
public String reservedTermsExportDisclaimer;
|
||||
public String rdapTos;
|
||||
@@ -106,6 +109,13 @@ public class RegistryConfigSettings {
|
||||
public Set<String> noPollMessageOnDeletionRegistrarIds;
|
||||
}
|
||||
|
||||
public static class DomainExpiryAccessPeriod {
|
||||
public long totalLengthSeconds;
|
||||
public long tierLengthSeconds;
|
||||
public Map<String, BigDecimal> initialFee;
|
||||
public Map<String, BigDecimal> finalFee;
|
||||
}
|
||||
|
||||
/** Configuration for Hibernate. */
|
||||
public static class Hibernate {
|
||||
public boolean allowNestedTransactions;
|
||||
@@ -181,6 +191,8 @@ public class RegistryConfigSettings {
|
||||
public String supportPhoneNumber;
|
||||
public String supportEmailAddress;
|
||||
public String technicalDocsUrl;
|
||||
public int historyQueryLimit;
|
||||
public int bulkDomainActionLimit;
|
||||
}
|
||||
|
||||
/** Configuration for monitoring. */
|
||||
@@ -188,6 +200,42 @@ public class RegistryConfigSettings {
|
||||
public int stackdriverMaxQps;
|
||||
public int stackdriverMaxPointsPerRequest;
|
||||
public int writeIntervalSeconds;
|
||||
public double frontendMetricsRatio;
|
||||
public double backendMetricsRatio;
|
||||
}
|
||||
|
||||
/** Configuration for EppServer. */
|
||||
public static class EppServer {
|
||||
public int port;
|
||||
public int healthCheckPort;
|
||||
public String sslPemFilename;
|
||||
public String sslPemBucket;
|
||||
public String kmsLocation;
|
||||
public String kmsKeyRing;
|
||||
public String kmsCryptoKey;
|
||||
public int maxMessageLengthBytes;
|
||||
public int headerLengthBytes;
|
||||
public int readTimeoutSeconds;
|
||||
public int maxConnectionsPerIp;
|
||||
public int maxConnectionsPerCert;
|
||||
public int serverCertificateCacheSeconds;
|
||||
public Quota quota;
|
||||
}
|
||||
|
||||
/** Configuration options that apply to quota management. */
|
||||
public static class Quota {
|
||||
|
||||
/** Quota configuration for a specific set of users. */
|
||||
public static class QuotaGroup {
|
||||
public List<String> userId;
|
||||
public int tokenAmount;
|
||||
public int refillSeconds;
|
||||
public int batchSize;
|
||||
}
|
||||
|
||||
public int refreshSeconds;
|
||||
public QuotaGroup defaultQuota;
|
||||
public List<QuotaGroup> customQuota;
|
||||
}
|
||||
|
||||
/** Miscellaneous configuration that doesn't quite fit in anywhere else. */
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<queue>
|
||||
<name>dns-refresh</name>
|
||||
<max-dispatches-per-second>100</max-dispatches-per-second>
|
||||
<max-retry-duration>82800s</max-retry-duration>
|
||||
</queue>
|
||||
|
||||
<!-- Queue for publishing DNS updates in batches. -->
|
||||
@@ -29,6 +30,7 @@
|
||||
<min-backoff>30s</min-backoff>
|
||||
<max-backoff>1800s</max-backoff>
|
||||
<max-doublings>0</max-doublings>
|
||||
<max-retry-duration>82800s</max-retry-duration>
|
||||
</queue>
|
||||
|
||||
<!-- Queue for uploading RDE deposits to the escrow provider. -->
|
||||
@@ -59,6 +61,7 @@
|
||||
<queue>
|
||||
<name>async-host-rename</name>
|
||||
<max-dispatches-per-second>1</max-dispatches-per-second>
|
||||
<max-retry-duration>82800s</max-retry-duration>
|
||||
</queue>
|
||||
|
||||
<!-- Queue for tasks that wait for a Beam pipeline to complete (i.e. Spec11 and invoicing). -->
|
||||
@@ -110,11 +113,12 @@
|
||||
<max-attempts>3</max-attempts>
|
||||
</queue>
|
||||
|
||||
<!-- <!– Queue for async actions that should be run at some point in the future. –>-->
|
||||
<!-- Queue for async actions that should be run at some point in the future.-->
|
||||
<queue>
|
||||
<name>async-actions</name>
|
||||
<max-dispatches-per-second>1</max-dispatches-per-second>
|
||||
<max-concurrent-dispatches>5</max-concurrent-dispatches>
|
||||
<max-retry-duration>82800s</max-retry-duration>
|
||||
</queue>
|
||||
|
||||
</entries>
|
||||
|
||||
@@ -88,6 +88,28 @@ registryPolicy:
|
||||
# registrar
|
||||
registryAdminClientId: TheRegistrar
|
||||
|
||||
# Configuration for the Expiry Access Period (XAP) that occurs immediately
|
||||
# following completion of a domain's deletion. This needs to be enabled on a
|
||||
# per-TLD basis as well.
|
||||
domainExpiryAccessPeriod:
|
||||
# The length of the total expiry access period; defaults to 10 days.
|
||||
totalLengthSeconds: 864000
|
||||
# The length of each tier during the expiry access period; defaults to 1
|
||||
# hour. The XAP fee remains constant for the duration of each tier. The
|
||||
# total length must be evenly divisible by the tier length, i.e. there
|
||||
# should be a whole number of tiers all the same length.
|
||||
tierLengthSeconds: 3600
|
||||
# The initial fee for the first tier when the expiry access period starts,
|
||||
# specified for each currency this registry platform uses.
|
||||
initialFee:
|
||||
USD: 100000
|
||||
JPY: 10000000
|
||||
# The final fee for the last tier when the expiry access period ends,
|
||||
# specified for each currency this registry platform uses.
|
||||
finalFee:
|
||||
USD: 10
|
||||
JPY: 1000
|
||||
|
||||
# Disclaimer at the top of the exported premium terms list.
|
||||
premiumTermsExportDisclaimer: |
|
||||
This list contains domains for the TLD offered at a premium price. This
|
||||
@@ -380,6 +402,12 @@ registrarConsole:
|
||||
# URL linking to directory of technical support docs on the registry.
|
||||
technicalDocsUrl: http://example.com/your_support_docs/
|
||||
|
||||
# Maximum number of history records returned in a single query.
|
||||
historyQueryLimit: 500
|
||||
|
||||
# Maximum number of domains allowed in a single bulk action.
|
||||
bulkDomainActionLimit: 500
|
||||
|
||||
monitoring:
|
||||
# Max queries per second for the Google Cloud Monitoring V3 (aka Stackdriver)
|
||||
# API. The limit can be adjusted by contacting Cloud Support.
|
||||
@@ -392,6 +420,57 @@ monitoring:
|
||||
# How often metrics are exported to BigQuery.
|
||||
writeIntervalSeconds: 60
|
||||
|
||||
# What ratio of frontend request metrics should be stochastically recorded
|
||||
# (0.0 means none, 1.0 means all). This is useful for reducing metrics volume,
|
||||
# and thus cost, while still recording some information for performance
|
||||
# monitoring purposes.
|
||||
frontendMetricsRatio: 1.0
|
||||
|
||||
# What ratio of backend request metrics should be stochastically recorded
|
||||
# (0.0 means none, 1.0 means all). This is useful for reducing metrics volume,
|
||||
# and thus cost, while still recording some information for performance
|
||||
# monitoring purposes.
|
||||
backendMetricsRatio: 1.0
|
||||
|
||||
eppServer:
|
||||
# The integrated EPP server listens on this port.
|
||||
port: 30002
|
||||
# Health check port used by GCP load balancer.
|
||||
healthCheckPort: 30000
|
||||
# GCS bucket that stores the encrypted PEM file.
|
||||
sslPemBucket: your-gcs-bucket
|
||||
# Name of the encrypted PEM file.
|
||||
sslPemFilename: your-pem-filename
|
||||
# Cloud KMS details for certificate decryption.
|
||||
kmsLocation: your-kms-location
|
||||
kmsKeyRing: your-kms-keyRing
|
||||
kmsCryptoKey: your-kms-cryptoKey
|
||||
|
||||
# Maximum input message length in bytes.
|
||||
maxMessageLengthBytes: 1073741824
|
||||
# Length of the header field in bytes (RFC 5734).
|
||||
headerLengthBytes: 4
|
||||
# Time after which an idle connection will be closed.
|
||||
readTimeoutSeconds: 3600
|
||||
# Max concurrent connections per IP address.
|
||||
maxConnectionsPerIp: 10
|
||||
# Max concurrent connections per authenticated certificate.
|
||||
maxConnectionsPerCert: 10
|
||||
# Server certificate cache duration.
|
||||
serverCertificateCacheSeconds: 1800
|
||||
|
||||
# Quota configuration for EPP
|
||||
quota:
|
||||
refreshSeconds: 0
|
||||
defaultQuota:
|
||||
userId: []
|
||||
tokenAmount: 100
|
||||
refillSeconds: 0
|
||||
# To implement a shared quota group across multiple registrars, place a virtual
|
||||
# group name as the FIRST element of the userId list.
|
||||
# e.g., userId: ["my_group", "registrar1", "registrar2"]
|
||||
customQuota: []
|
||||
|
||||
misc:
|
||||
# The ID of the Google Sheet (as found in the URL) to export registrar details
|
||||
# to. Leave this null to disable syncing.
|
||||
|
||||
+10
-10
@@ -266,16 +266,6 @@
|
||||
<schedule>0 15 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/wipeOutContactHistoryPii]]></url>
|
||||
<name>wipeOutContactHistoryPii</name>
|
||||
<description>
|
||||
This job runs weekly to wipe out PII fields of ContactHistory entities
|
||||
that have been in the database for a certain period of time.
|
||||
</description>
|
||||
<schedule>0 15 * * 1</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/bsaDownload]]></url>
|
||||
<name>bsaDownload</name>
|
||||
@@ -333,4 +323,14 @@
|
||||
<schedule>*/5 * * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/syncRemoteCache]]></url>
|
||||
<name>syncRemoteCache</name>
|
||||
<description>
|
||||
Syncs remote (Valkey/Redis) EPP resource caches with changes made recently.
|
||||
</description>
|
||||
<!-- Runs every 5 minutes. RDAP needs to be updated within 60 minutes of changes. -->
|
||||
<schedule>*/5 * * * *</schedule>
|
||||
</task>
|
||||
|
||||
</entries>
|
||||
|
||||
-10
@@ -155,16 +155,6 @@
|
||||
<schedule>*/1 * * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/wipeOutContactHistoryPii]]></url>
|
||||
<name>wipeOutContactHistoryPii</name>
|
||||
<description>
|
||||
This job runs weekly to wipe out PII fields of ContactHistory entities
|
||||
that have been in the database for a certain period of time.
|
||||
</description>
|
||||
<schedule>0 15 * * 1</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/bsaDownload]]></url>
|
||||
<name>bsaDownload</name>
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
// Copyright 2017 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.eppserver;
|
||||
|
||||
import static google.registry.util.ResourceUtils.readResourceBytes;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoSet;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.config.RegistryConfigSettings;
|
||||
import google.registry.eppserver.Protocol.FrontendProtocol;
|
||||
import google.registry.eppserver.handler.EppProxyProtocolHandler;
|
||||
import google.registry.eppserver.handler.EppServiceHandler;
|
||||
import google.registry.eppserver.quota.QuotaManager;
|
||||
import google.registry.networking.handler.SslServerInitializer;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
|
||||
import io.netty.handler.codec.LengthFieldPrepender;
|
||||
import io.netty.handler.ssl.SslProvider;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.inject.Provider;
|
||||
import jakarta.inject.Qualifier;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import redis.clients.jedis.UnifiedJedis;
|
||||
|
||||
/** A module that provides the {@link FrontendProtocol} used for epp protocol. */
|
||||
@Module
|
||||
public final class EppProtocolModule {
|
||||
|
||||
private EppProtocolModule() {}
|
||||
|
||||
@Qualifier
|
||||
public @interface EppProtocol {}
|
||||
|
||||
@Qualifier
|
||||
public @interface IpQuota {}
|
||||
|
||||
@Qualifier
|
||||
public @interface CommandQuota {}
|
||||
|
||||
private static final String PROTOCOL_NAME = "epp";
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@IntoSet
|
||||
static FrontendProtocol provideProtocol(
|
||||
@EppProtocol int eppPort,
|
||||
@EppProtocol ImmutableList<Provider<? extends ChannelHandler>> handlerProviders) {
|
||||
return Protocol.frontendBuilder()
|
||||
.name(PROTOCOL_NAME)
|
||||
.port(eppPort)
|
||||
.handlerProviders(handlerProviders)
|
||||
.hasBackend(false)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@EppProtocol
|
||||
static ImmutableList<Provider<? extends ChannelHandler>> provideHandlerProviders(
|
||||
Provider<EppProxyProtocolHandler> proxyProtocolHandlerProvider,
|
||||
@EppProtocol Provider<SslServerInitializer<NioSocketChannel>> sslServerInitializerProvider,
|
||||
@EppProtocol Provider<ReadTimeoutHandler> readTimeoutHandlerProvider,
|
||||
Provider<LengthFieldBasedFrameDecoder> lengthFieldBasedFrameDecoderProvider,
|
||||
Provider<LengthFieldPrepender> lengthFieldPrependerProvider,
|
||||
Provider<EppServiceHandler> eppServiceHandlerProvider) {
|
||||
return ImmutableList.of(
|
||||
proxyProtocolHandlerProvider,
|
||||
sslServerInitializerProvider,
|
||||
readTimeoutHandlerProvider,
|
||||
lengthFieldBasedFrameDecoderProvider,
|
||||
lengthFieldPrependerProvider,
|
||||
eppServiceHandlerProvider);
|
||||
}
|
||||
|
||||
@Provides
|
||||
static LengthFieldBasedFrameDecoder provideLengthFieldBasedFrameDecoder(
|
||||
@Config("eppServerMaxMessageLengthBytes") int maxMessageLengthBytes,
|
||||
@Config("eppServerHeaderLengthBytes") int headerLengthBytes) {
|
||||
return new LengthFieldBasedFrameDecoder(
|
||||
maxMessageLengthBytes, 0, headerLengthBytes, -headerLengthBytes, headerLengthBytes);
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
static LengthFieldPrepender provideLengthFieldPrepender(
|
||||
@Config("eppServerHeaderLengthBytes") int headerLengthBytes) {
|
||||
return new LengthFieldPrepender(headerLengthBytes, true);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@EppProtocol
|
||||
static ReadTimeoutHandler provideReadTimeoutHandler(
|
||||
@Config("eppServerReadTimeoutSeconds") int readTimeoutSeconds) {
|
||||
return new ReadTimeoutHandler(readTimeoutSeconds);
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named("hello")
|
||||
static byte[] provideHelloBytes() {
|
||||
try {
|
||||
return readResourceBytes(EppProtocolModule.class, "hello.xml").read();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read EPP <hello> message file.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@EppProtocol
|
||||
static SslServerInitializer<NioSocketChannel> provideSslServerInitializer(
|
||||
SslProvider sslProvider,
|
||||
Supplier<PrivateKey> privateKeySupplier,
|
||||
Supplier<ImmutableList<X509Certificate>> certificatesSupplier) {
|
||||
return new SslServerInitializer<>(
|
||||
true, false, sslProvider, privateKeySupplier, certificatesSupplier);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@CommandQuota
|
||||
static QuotaManager provideCommandQuotaManager(
|
||||
@Config("eppServerQuota") RegistryConfigSettings.Quota quota, Optional<UnifiedJedis> jedis) {
|
||||
return new QuotaManager(quota, jedis.orElse(null), "command");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
// Copyright 2024 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.eppserver;
|
||||
|
||||
import static google.registry.eppserver.Protocol.PROTOCOL_KEY;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.eppserver.EppServerModule.EppServerComponent;
|
||||
import google.registry.eppserver.Protocol.FrontendProtocol;
|
||||
import google.registry.eppserver.handler.EppServiceHandler;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.util.concurrent.DefaultEventExecutorGroup;
|
||||
import io.netty.util.concurrent.EventExecutorGroup;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import jakarta.inject.Provider;
|
||||
import java.util.HashMap;
|
||||
|
||||
/** An integrated EPP server that listens for EPP traffic and processes it in-process. */
|
||||
public class EppServer {
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
/** Maximum length of the queue of incoming connections. */
|
||||
private static final int MAX_SOCKET_BACKLOG = 128;
|
||||
|
||||
private final ImmutableSet<FrontendProtocol> protocols;
|
||||
private final HashMap<Integer, Channel> portToChannelMap = new HashMap<>();
|
||||
private final EventLoopGroup eventGroup = new NioEventLoopGroup();
|
||||
private final EventExecutorGroup businessGroup =
|
||||
new DefaultEventExecutorGroup(Math.max(4, Runtime.getRuntime().availableProcessors() * 4));
|
||||
|
||||
public EppServer(EppServerComponent eppServerComponent) {
|
||||
this.protocols = ImmutableSet.copyOf(eppServerComponent.protocols());
|
||||
}
|
||||
|
||||
private class ServerChannelInitializer extends ChannelInitializer<NioSocketChannel> {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel inboundChannel) {
|
||||
FrontendProtocol inboundProtocol =
|
||||
(FrontendProtocol) inboundChannel.parent().attr(PROTOCOL_KEY).get();
|
||||
inboundChannel.attr(PROTOCOL_KEY).set(inboundProtocol);
|
||||
|
||||
addHandlers(inboundChannel.pipeline(), inboundProtocol.handlerProviders());
|
||||
|
||||
// Start reading immediately since we don't have a backend relay.
|
||||
inboundChannel.config().setAutoRead(true);
|
||||
|
||||
logger.atInfo().log("Connection established: %s %s", inboundProtocol.name(), inboundChannel);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void addHandlers(
|
||||
ChannelPipeline channelPipeline,
|
||||
ImmutableList<Provider<? extends ChannelHandler>> handlerProviders) {
|
||||
for (Provider<? extends ChannelHandler> handlerProvider : handlerProviders) {
|
||||
ChannelHandler handler = handlerProvider.get();
|
||||
String handlerName = handler.getClass().getSimpleName();
|
||||
if (handler.getClass() == EppServiceHandler.class) {
|
||||
channelPipeline.addLast(businessGroup, handlerName, handler);
|
||||
} else {
|
||||
channelPipeline.addLast(handlerName, handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void start() {
|
||||
ServerBootstrap serverBootstrap =
|
||||
new ServerBootstrap()
|
||||
.group(eventGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.childHandler(new ServerChannelInitializer())
|
||||
.option(ChannelOption.SO_BACKLOG, MAX_SOCKET_BACKLOG)
|
||||
.childOption(ChannelOption.SO_KEEPALIVE, true);
|
||||
|
||||
protocols.forEach(
|
||||
protocol -> {
|
||||
int port = protocol.port();
|
||||
try {
|
||||
ChannelFuture serverChannelFuture = serverBootstrap.bind(port).sync();
|
||||
if (serverChannelFuture.isSuccess()) {
|
||||
logger.atInfo().log(
|
||||
"Start listening on port %s for %s protocol.", port, protocol.name());
|
||||
Channel serverChannel = serverChannelFuture.channel();
|
||||
serverChannel.attr(PROTOCOL_KEY).set(protocol);
|
||||
portToChannelMap.put(port, serverChannel);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
logger.atSevere().withCause(e).log(
|
||||
"Cannot listen on port %d for %s protocol.", port, protocol.name());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
logger.atInfo().log("Shutting down EPP server...");
|
||||
portToChannelMap
|
||||
.values()
|
||||
.forEach(
|
||||
channel -> {
|
||||
Future<?> unusedFuture = channel.close();
|
||||
});
|
||||
Future<?> unusedFuture = eventGroup.shutdownGracefully();
|
||||
Future<?> unusedFutureBusiness = businessGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
// Copyright 2017 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.eppserver;
|
||||
|
||||
import com.google.api.services.cloudkms.v1.CloudKMS;
|
||||
import com.google.api.services.cloudkms.v1.model.DecryptRequest;
|
||||
import com.google.auth.oauth2.GoogleCredentials;
|
||||
import com.google.cloud.http.HttpTransportOptions;
|
||||
import com.google.cloud.storage.BlobId;
|
||||
import com.google.cloud.storage.Storage;
|
||||
import com.google.cloud.storage.StorageException;
|
||||
import com.google.cloud.storage.StorageOptions;
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.google.monitoring.metrics.MetricReporter;
|
||||
import dagger.Component;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.cache.CacheModule;
|
||||
import google.registry.config.CredentialModule;
|
||||
import google.registry.config.CredentialModule.ApplicationDefaultCredential;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.config.RegistryConfig.ConfigModule;
|
||||
import google.registry.eppserver.EppProtocolModule.EppProtocol;
|
||||
import google.registry.eppserver.HealthCheckProtocolModule.HealthCheckProtocol;
|
||||
import google.registry.eppserver.Protocol.FrontendProtocol;
|
||||
import google.registry.keyring.KeyringModule;
|
||||
import google.registry.keyring.api.KeyModule;
|
||||
import google.registry.networking.module.CertificateSupplierModule;
|
||||
import google.registry.networking.module.CertificateSupplierModule.Mode;
|
||||
import google.registry.privileges.secretmanager.SecretManagerModule;
|
||||
import google.registry.util.GoogleCredentialsBundle;
|
||||
import google.registry.util.OidcTokenUtils;
|
||||
import google.registry.util.RegistryEnvironment;
|
||||
import google.registry.util.UtilsModule;
|
||||
import io.netty.handler.logging.LogLevel;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import io.netty.handler.ssl.OpenSsl;
|
||||
import io.netty.handler.ssl.SslProvider;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.Base64;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
import redis.clients.jedis.UnifiedJedis;
|
||||
|
||||
/**
|
||||
* A module that provides the port-to-protocol map and other configs that are used to bootstrap the
|
||||
* server.
|
||||
*/
|
||||
@Module
|
||||
public class EppServerModule {
|
||||
|
||||
@Provides
|
||||
@EppProtocol
|
||||
static int provideEppPort(@Config("eppServerPort") int eppPort) {
|
||||
return eppPort;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@HealthCheckProtocol
|
||||
static int provideHealthCheckPort(@Config("eppServerHealthCheckPort") int healthCheckPort) {
|
||||
return healthCheckPort;
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
LoggingHandler provideLoggingHandler() {
|
||||
return new LoggingHandler(LogLevel.DEBUG);
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
static Supplier<GoogleCredentials> provideRefreshedCredentialsSupplier(
|
||||
@ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle) {
|
||||
return () -> {
|
||||
GoogleCredentials credentials = credentialsBundle.getGoogleCredentials();
|
||||
try {
|
||||
credentials.refreshIfExpired();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot refresh credentials.", e);
|
||||
}
|
||||
return credentials;
|
||||
};
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named("idToken")
|
||||
static Supplier<String> provideOidcToken(
|
||||
@ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle,
|
||||
@Config("oauthClientId") String clientId) {
|
||||
return Suppliers.memoizeWithExpiration(
|
||||
() -> OidcTokenUtils.createOidcToken(credentialsBundle, clientId), 1, TimeUnit.HOURS);
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named("canary")
|
||||
static boolean provideIsCanary() {
|
||||
return RegistryEnvironment.get().name().endsWith("_CANARY");
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
static CloudKMS provideCloudKms(
|
||||
@ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle,
|
||||
@Config("projectId") String projectId) {
|
||||
return new CloudKMS.Builder(
|
||||
credentialsBundle.getHttpTransport(),
|
||||
credentialsBundle.getJsonFactory(),
|
||||
credentialsBundle.getHttpRequestInitializer())
|
||||
.setApplicationName(projectId)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
static Storage provideStorage(
|
||||
@ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle) {
|
||||
return StorageOptions.newBuilder()
|
||||
.setTransportOptions(
|
||||
HttpTransportOptions.newBuilder()
|
||||
.setHttpTransportFactory(credentialsBundle::getHttpTransport)
|
||||
.build())
|
||||
.setCredentials(credentialsBundle.getGoogleCredentials())
|
||||
.build()
|
||||
.getService();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("encryptedPemBytes")
|
||||
static byte[] provideEncryptedPemBytes(
|
||||
Storage storage,
|
||||
@Config("eppServerSslPemBucket") String bucket,
|
||||
@Config("eppServerSslPemFilename") String sslPemFilename) {
|
||||
try {
|
||||
return Base64.getMimeDecoder()
|
||||
.decode(storage.readAllBytes(BlobId.of(bucket, sslPemFilename)));
|
||||
} catch (StorageException e) {
|
||||
throw new RuntimeException(
|
||||
String.format(
|
||||
"Error reading encrypted PEM file %s from GCS bucket %s", sslPemFilename, bucket),
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("pemBytes")
|
||||
static byte[] providePemBytes(
|
||||
CloudKMS cloudKms,
|
||||
@Named("encryptedPemBytes") byte[] encryptedPemBytes,
|
||||
@Config("projectId") String projectId,
|
||||
@Config("eppServerKmsLocation") String location,
|
||||
@Config("eppServerKmsKeyRing") String keyRing,
|
||||
@Config("eppServerKmsCryptoKey") String cryptoKey) {
|
||||
String cryptoKeyUrl =
|
||||
String.format(
|
||||
"projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s",
|
||||
projectId, location, keyRing, cryptoKey);
|
||||
try {
|
||||
DecryptRequest decryptRequest = new DecryptRequest().encodeCiphertext(encryptedPemBytes);
|
||||
return cloudKms
|
||||
.projects()
|
||||
.locations()
|
||||
.keyRings()
|
||||
.cryptoKeys()
|
||||
.decrypt(cryptoKeyUrl, decryptRequest)
|
||||
.execute()
|
||||
.decodePlaintext();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(
|
||||
String.format("PEM file decryption failed using CryptoKey: %s", cryptoKeyUrl), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
static SslProvider provideSslProvider() {
|
||||
return OpenSsl.isAvailable() ? SslProvider.OPENSSL : SslProvider.JDK;
|
||||
}
|
||||
|
||||
@Provides
|
||||
static ExecutorService provideExecutorService() {
|
||||
return Executors.newWorkStealingPool();
|
||||
}
|
||||
|
||||
@Provides
|
||||
static ScheduledExecutorService provideScheduledExecutorService() {
|
||||
return Executors.newSingleThreadScheduledExecutor();
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
static Mode provideMode() {
|
||||
if (RegistryEnvironment.get() == RegistryEnvironment.LOCAL) {
|
||||
return Mode.SELF_SIGNED;
|
||||
} else {
|
||||
return Mode.PEM_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named("remoteCertCachingDuration")
|
||||
static Duration provideCertCachingDuration(
|
||||
@Config("eppServerCertificateCacheSeconds") int cacheSeconds) {
|
||||
return Duration.ofSeconds(cacheSeconds);
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named("frontendMetricsRatio")
|
||||
static double provideFrontendMetricsRatio(@Config("eppServerFrontendMetricsRatio") double ratio) {
|
||||
return ratio;
|
||||
}
|
||||
|
||||
/** Root level component that exposes the port-to-protocol map. */
|
||||
@Singleton
|
||||
@Component(
|
||||
modules = {
|
||||
EppServerModule.class,
|
||||
CacheModule.class,
|
||||
CertificateSupplierModule.class,
|
||||
ConfigModule.class,
|
||||
EppProtocolModule.class,
|
||||
HealthCheckProtocolModule.class,
|
||||
MetricsModule.class,
|
||||
CredentialModule.class,
|
||||
KeyModule.class,
|
||||
KeyringModule.class,
|
||||
SecretManagerModule.class,
|
||||
UtilsModule.class
|
||||
})
|
||||
public interface EppServerComponent {
|
||||
Set<FrontendProtocol> protocols();
|
||||
|
||||
MetricReporter metricReporter();
|
||||
|
||||
Optional<UnifiedJedis> jedis();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2017 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.eppserver;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoSet;
|
||||
import google.registry.eppserver.Protocol.FrontendProtocol;
|
||||
import google.registry.eppserver.handler.HealthCheckHandler;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import jakarta.inject.Provider;
|
||||
import jakarta.inject.Qualifier;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
/** A module that provides the {@link FrontendProtocol} used for health check protocol. */
|
||||
@Module
|
||||
public final class HealthCheckProtocolModule {
|
||||
|
||||
/** Dagger qualifier to provide health check protocol related handlers and other bindings. */
|
||||
@Qualifier
|
||||
public @interface HealthCheckProtocol {}
|
||||
|
||||
private static final String PROTOCOL_NAME = "health_check";
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@IntoSet
|
||||
static FrontendProtocol provideProtocol(
|
||||
@HealthCheckProtocol int healthCheckPort,
|
||||
@HealthCheckProtocol ImmutableList<Provider<? extends ChannelHandler>> handlerProviders) {
|
||||
return Protocol.frontendBuilder()
|
||||
.name(PROTOCOL_NAME)
|
||||
.port(healthCheckPort)
|
||||
.handlerProviders(handlerProviders)
|
||||
.hasBackend(false)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@HealthCheckProtocol
|
||||
static ImmutableList<Provider<? extends ChannelHandler>> provideHandlerProviders(
|
||||
@HealthCheckProtocol Provider<HealthCheckHandler> healthCheckHandlerProvider) {
|
||||
return ImmutableList.of(healthCheckHandlerProvider);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@HealthCheckProtocol
|
||||
static HealthCheckHandler provideHealthCheckHandler() {
|
||||
// These are currently hardcoded in the handler or defaulted in RegistryConfig.
|
||||
// For now, we use the standard GCP health check strings.
|
||||
return new HealthCheckHandler("HEALTH_CHECK_REQUEST", "HEALTH_CHECK_RESPONSE");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
// Copyright 2017 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.eppserver;
|
||||
|
||||
import com.google.api.services.monitoring.v3.Monitoring;
|
||||
import com.google.api.services.monitoring.v3.model.MonitoredResource;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.google.monitoring.metrics.MetricReporter;
|
||||
import com.google.monitoring.metrics.MetricWriter;
|
||||
import com.google.monitoring.metrics.stackdriver.StackdriverWriter;
|
||||
import dagger.Component;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.config.CredentialModule;
|
||||
import google.registry.config.CredentialModule.ApplicationDefaultCredential;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.config.RegistryConfig.ConfigModule;
|
||||
import google.registry.util.GoogleCredentialsBundle;
|
||||
import google.registry.util.MetricParameters;
|
||||
import google.registry.util.RegistryEnvironment;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
/** Module that provides necessary bindings to instantiate a {@link MetricReporter} */
|
||||
@Module
|
||||
public class MetricsModule {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
static Monitoring provideMonitoring(
|
||||
@ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle,
|
||||
@Config("projectId") String projectId) {
|
||||
return new Monitoring.Builder(
|
||||
credentialsBundle.getHttpTransport(),
|
||||
credentialsBundle.getJsonFactory(),
|
||||
credentialsBundle.getHttpRequestInitializer())
|
||||
.setApplicationName(projectId)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
static MetricWriter provideMetricWriter(
|
||||
Monitoring monitoringClient,
|
||||
MonitoredResource monitoredResource,
|
||||
@Config("projectId") String projectId,
|
||||
@Config("stackdriverMaxQps") int stackdriverMaxQps,
|
||||
@Config("stackdriverMaxPointsPerRequest") int stackdriverMaxPointsPerRequest) {
|
||||
return new StackdriverWriter(
|
||||
monitoringClient,
|
||||
projectId,
|
||||
monitoredResource,
|
||||
stackdriverMaxQps,
|
||||
stackdriverMaxPointsPerRequest);
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
static MetricReporter provideMetricReporter(
|
||||
MetricWriter metricWriter, @Config("writeIntervalSeconds") int writeIntervalSeconds) {
|
||||
return new MetricReporter(
|
||||
metricWriter, writeIntervalSeconds, new ThreadFactoryBuilder().setDaemon(true).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a {@link MonitoredResource} appropriate for environment the EPP server runs in.
|
||||
*
|
||||
* <p>When running locally, the type of the monitored resource is set to {@code global}, otherwise
|
||||
* it is {@code gke_container}.
|
||||
*
|
||||
* @see <a
|
||||
* href="https://cloud.google.com/monitoring/custom-metrics/creating-metrics#which-resource">
|
||||
* Choosing a monitored resource type</a>
|
||||
*/
|
||||
@Singleton
|
||||
@Provides
|
||||
static MonitoredResource provideMonitoredResource(
|
||||
@Config("projectId") String projectId, MetricParameters metricParameters) {
|
||||
MonitoredResource monitoredResource = new MonitoredResource();
|
||||
if (RegistryEnvironment.get() == RegistryEnvironment.LOCAL) {
|
||||
monitoredResource.setType("global").setLabels(ImmutableMap.of("project_id", projectId));
|
||||
} else {
|
||||
monitoredResource.setType("gke_container").setLabels(metricParameters.makeLabelsMap());
|
||||
}
|
||||
logger.atInfo().log("Monitored resource: %s", monitoredResource);
|
||||
return monitoredResource;
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Component(
|
||||
modules = {
|
||||
MetricsModule.class,
|
||||
EppServerModule.class,
|
||||
CredentialModule.class,
|
||||
ConfigModule.class
|
||||
})
|
||||
interface MetricsComponent {
|
||||
MetricReporter metricReporter();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// Copyright 2017 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.eppserver;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.util.Attribute;
|
||||
import io.netty.util.AttributeKey;
|
||||
import jakarta.inject.Provider;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** Value class that encapsulates parameters of a specific connection. */
|
||||
public interface Protocol {
|
||||
|
||||
/** Key used to retrieve the {@link Protocol} from a {@link Channel}'s {@link Attribute}. */
|
||||
AttributeKey<Protocol> PROTOCOL_KEY = AttributeKey.valueOf("PROTOCOL_KEY");
|
||||
|
||||
/** Protocol name. */
|
||||
String name();
|
||||
|
||||
/**
|
||||
* Port to bind to (for {@link FrontendProtocol}) or to connect to (for {@link BackendProtocol}).
|
||||
*/
|
||||
int port();
|
||||
|
||||
/** The {@link ChannelHandler} providers to use for the protocol, in order. */
|
||||
ImmutableList<Provider<? extends ChannelHandler>> handlerProviders();
|
||||
|
||||
/** A builder for {@link FrontendProtocol}, by default there is a backend associated with it. */
|
||||
static FrontendProtocol.Builder frontendBuilder() {
|
||||
return new AutoValue_Protocol_FrontendProtocol.Builder().hasBackend(true);
|
||||
}
|
||||
|
||||
/** A builder for {@link FrontendProtocol}, by default it connects to a remote host. */
|
||||
static BackendProtocol.Builder backendBuilder() {
|
||||
return new AutoValue_Protocol_BackendProtocol.Builder().isLocal(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic builder enabling chaining for concrete implementations.
|
||||
*
|
||||
* @param <B> builder of the concrete subtype of {@link Protocol}.
|
||||
* @param <P> type of the concrete subtype of {@link Protocol}.
|
||||
*/
|
||||
abstract class Builder<B extends Builder<B, P>, P extends Protocol> {
|
||||
|
||||
public abstract B name(String value);
|
||||
|
||||
public abstract B port(int port);
|
||||
|
||||
public abstract B handlerProviders(ImmutableList<Provider<? extends ChannelHandler>> value);
|
||||
|
||||
public abstract P build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Connection parameters for a connection from the client to the proxy.
|
||||
*
|
||||
* <p>This protocol is associated to a {@link NioSocketChannel} established by remote peer
|
||||
* connecting to the given {@code port} that the proxy is listening on.
|
||||
*/
|
||||
@AutoValue
|
||||
abstract class FrontendProtocol implements Protocol {
|
||||
|
||||
/**
|
||||
* The {@link BackendProtocol} used to establish a relay channel and relay the traffic to. Not
|
||||
* required for health check protocol or HTTP(S) redirect.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract BackendProtocol relayProtocol();
|
||||
|
||||
/**
|
||||
* Whether this {@code FrontendProtocol} relays to a {@code BackendProtocol}. All proxied
|
||||
* traffic must be represented by a protocol that has a backend.
|
||||
*/
|
||||
public abstract boolean hasBackend();
|
||||
|
||||
@AutoValue.Builder
|
||||
public abstract static class Builder extends Protocol.Builder<Builder, FrontendProtocol> {
|
||||
public abstract Builder relayProtocol(BackendProtocol value);
|
||||
|
||||
public abstract Builder hasBackend(boolean value);
|
||||
|
||||
abstract FrontendProtocol autoBuild();
|
||||
|
||||
@Override
|
||||
public FrontendProtocol build() {
|
||||
FrontendProtocol frontendProtocol = autoBuild();
|
||||
Preconditions.checkState(
|
||||
!frontendProtocol.hasBackend() || frontendProtocol.relayProtocol() != null,
|
||||
"Frontend protocol %s must define a relay protocol.",
|
||||
frontendProtocol.name());
|
||||
return frontendProtocol;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connection parameters for a connection from the EPP server to Nomulus.
|
||||
*
|
||||
* <p>This protocol is associated to a {@link NioSocketChannel} established by the EPP server
|
||||
* connecting to a remote peer.
|
||||
*/
|
||||
@AutoValue
|
||||
abstract class BackendProtocol implements Protocol {
|
||||
/** The hostname that the EPP server connects to. */
|
||||
public abstract String host();
|
||||
|
||||
/** Whether the protocol is expected to connect to localhost. */
|
||||
public abstract boolean isLocal();
|
||||
|
||||
/** Builder of {@link BackendProtocol}. */
|
||||
@AutoValue.Builder
|
||||
public abstract static class Builder extends Protocol.Builder<Builder, BackendProtocol> {
|
||||
public abstract Builder host(String value);
|
||||
|
||||
public abstract Builder isLocal(boolean value);
|
||||
|
||||
abstract BackendProtocol autoBuild();
|
||||
|
||||
@Override
|
||||
public BackendProtocol build() {
|
||||
BackendProtocol protocol = autoBuild();
|
||||
Preconditions.checkState(
|
||||
!protocol.isLocal() || protocol.host().equals("localhost"),
|
||||
"Local backend protocol must connect to localhost");
|
||||
return autoBuild();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
// Copyright 2017 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.eppserver.handler;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.netty.util.AttributeKey;
|
||||
import jakarta.inject.Inject;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Handler that processes possible existence of a PROXY protocol v1 header.
|
||||
*
|
||||
* <p>When an EPP client connects to the registry (through the proxy), the registry performs two
|
||||
* validations to ensure that only known registrars are allowed. First it checks the sha265 hash of
|
||||
* the client SSL certificate and match it to the hash stored in the database for the registrar. It
|
||||
* then checks if the connection is from an allow-listed IP address that belongs to that registrar.
|
||||
*
|
||||
* <p>The proxy receives client connects via the GCP load balancer, which results in the loss of
|
||||
* original client IP from the channel. Luckily, the load balancer supports the PROXY protocol v1,
|
||||
* which adds a header with source IP information, among other things, to the TCP request at the
|
||||
* start of the connection.
|
||||
*
|
||||
* <p>This handler determines if a connection is proxied (PROXY protocol v1 header present) and
|
||||
* correctly sets the source IP address to the channel's attribute regardless of whether it is
|
||||
* proxied. After that it removes itself from the channel pipeline because the proxy header is only
|
||||
* present at the beginning of the connection.
|
||||
*
|
||||
* <p>This handler must be the very first handler in a protocol, even before SSL handlers, because
|
||||
* PROXY protocol header comes as the very first thing, even before SSL handshake request.
|
||||
*
|
||||
* @see <a href="https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt">The PROXY protocol</a>
|
||||
*/
|
||||
public class EppProxyProtocolHandler extends ByteToMessageDecoder {
|
||||
|
||||
/** Key used to retrieve origin IP address from a channel's attribute. */
|
||||
public static final AttributeKey<String> REMOTE_ADDRESS_KEY =
|
||||
AttributeKey.valueOf("REMOTE_ADDRESS_KEY");
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
// The proxy header must start with this prefix.
|
||||
// Sample header: "PROXY TCP4 255.255.255.255 255.255.255.255 65535 65535\r\n".
|
||||
private static final byte[] HEADER_PREFIX = "PROXY".getBytes(US_ASCII);
|
||||
|
||||
private boolean finished = false;
|
||||
private String proxyHeader = null;
|
||||
|
||||
@Inject
|
||||
EppProxyProtocolHandler() {}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
super.channelRead(ctx, msg);
|
||||
if (finished) {
|
||||
String remoteIP;
|
||||
if (proxyHeader != null) {
|
||||
logger.atFine().log("PROXIED CONNECTION: %s", ctx.channel());
|
||||
logger.atFine().log("PROXY HEADER for channel %s: %s", ctx.channel(), proxyHeader);
|
||||
String[] headerArray = proxyHeader.split(" ", -1);
|
||||
if (headerArray.length == 6) {
|
||||
remoteIP = headerArray[2];
|
||||
logger.atFine().log(
|
||||
"Header parsed, using %s as remote IP for channel %s", remoteIP, ctx.channel());
|
||||
// If the header is "PROXY UNKNOWN"
|
||||
// (see https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt), likely when the
|
||||
// remote connection to the external load balancer is through special means, make it
|
||||
// 0.0.0.0 so that it can be treated accordingly by the relevant quota configs.
|
||||
} else if (headerArray.length == 2 && headerArray[1].equals("UNKNOWN")) {
|
||||
logger.atFine().log(
|
||||
"Header parsed, source IP unknown, using 0.0.0.0 as remote IP for channel %s",
|
||||
ctx.channel());
|
||||
remoteIP = "0.0.0.0";
|
||||
} else {
|
||||
logger.atFine().log(
|
||||
"Cannot parse the header, using source IP as remote IP for channel %s",
|
||||
ctx.channel());
|
||||
remoteIP = getSourceIP(ctx);
|
||||
}
|
||||
} else {
|
||||
logger.atFine().log(
|
||||
"No header present, using source IP directly for channel %s", ctx.channel());
|
||||
remoteIP = getSourceIP(ctx);
|
||||
}
|
||||
if (remoteIP != null) {
|
||||
ctx.channel().attr(REMOTE_ADDRESS_KEY).set(remoteIP);
|
||||
} else {
|
||||
logger.atWarning().log("Not able to obtain remote IP for channel %s", ctx.channel());
|
||||
}
|
||||
// ByteToMessageDecoder automatically flushes unread bytes in the ByteBuf to the next handler
|
||||
// when itself is being removed.
|
||||
ctx.pipeline().remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getSourceIP(ChannelHandlerContext ctx) {
|
||||
SocketAddress remoteAddress = ctx.channel().remoteAddress();
|
||||
return (remoteAddress instanceof InetSocketAddress inetSocketAddress)
|
||||
? inetSocketAddress.getAddress().getHostAddress()
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to decode an internally accumulated buffer and find the proxy protocol header.
|
||||
*
|
||||
* <p>When the connection is not proxied (i. e. the initial bytes are not "PROXY"), simply set
|
||||
* {@link #finished} to true and allow the handler to be removed. Otherwise the handler waits
|
||||
* until there's enough bytes to parse the header, save the parsed header to {@link #proxyHeader},
|
||||
* and then mark {@link #finished}.
|
||||
*
|
||||
* @param in internally accumulated buffer, newly arrived bytes are appended to it.
|
||||
* @param out objects passed to the next handler, in this case nothing is ever passed because the
|
||||
* header itself is processed and written to the attribute of the proxy, and the handler is
|
||||
* then removed from the pipeline.
|
||||
*/
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
|
||||
// Wait until there are more bytes available than the header's length before processing.
|
||||
if (in.readableBytes() >= HEADER_PREFIX.length) {
|
||||
if (containsHeader(in)) {
|
||||
// The inbound message contains the header, it must be a proxied connection. Note that
|
||||
// currently proxied connection is only used for EPP protocol, which requires the connection
|
||||
// to be SSL enabled. So the beginning of the inbound message upon connection can only be
|
||||
// either the proxy header (when proxied), or SSL handshake request (when not proxied),
|
||||
// which does not start with "PROXY". Therefore it is safe to assume that if the beginning
|
||||
// of the message contains "PROXY", it must be proxied, and must contain \r\n.
|
||||
int eol = findEndOfLine(in);
|
||||
// If eol is not found, that is because that we do not yet have enough inbound message, do
|
||||
// nothing and wait for more bytes to be readable. eol will eventually be positive because
|
||||
// of the reasoning above: The connection starts with "PROXY", so it must be a proxied
|
||||
// connection and contain \r\n.
|
||||
if (eol >= 0) {
|
||||
// ByteBuf.readBytes is called so that the header is processed and not passed to handlers
|
||||
// further in the pipeline.
|
||||
byte[] headerBytes = new byte[eol];
|
||||
in.readBytes(headerBytes);
|
||||
proxyHeader = new String(headerBytes, US_ASCII);
|
||||
// Skip \r\n.
|
||||
in.skipBytes(2);
|
||||
// Proxy header processed, mark finished so that this handler is removed.
|
||||
finished = true;
|
||||
}
|
||||
} else {
|
||||
// The inbound message does not contain a proxy header, mark finished so that this handler
|
||||
// is removed. Note that no inbound bytes are actually processed by this handler because we
|
||||
// did not call ByteBuf.readBytes(), but ByteBuf.getByte(), which does not change reader
|
||||
// index of the ByteBuf. So any inbound byte is then passed to the next handler to process.
|
||||
finished = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index in the buffer of the end of line found. Returns -1 if no end of line was
|
||||
* found in the buffer.
|
||||
*/
|
||||
private static int findEndOfLine(final ByteBuf buffer) {
|
||||
final int n = buffer.writerIndex();
|
||||
for (int i = buffer.readerIndex(); i < n; i++) {
|
||||
final byte b = buffer.getByte(i);
|
||||
if (b == '\r' && i < n - 1 && buffer.getByte(i + 1) == '\n') {
|
||||
return i; // \r\n
|
||||
}
|
||||
}
|
||||
return -1; // Not found.
|
||||
}
|
||||
|
||||
/** Checks if the given buffer contains the proxy header prefix. */
|
||||
private boolean containsHeader(ByteBuf buffer) {
|
||||
// The readable bytes is always more or equal to the size of the header prefix because this
|
||||
// method is only called when this condition is true.
|
||||
checkState(buffer.readableBytes() >= HEADER_PREFIX.length);
|
||||
for (int i = 0; i < HEADER_PREFIX.length; ++i) {
|
||||
if (buffer.getByte(buffer.readerIndex() + i) != HEADER_PREFIX[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
// Copyright 2026 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.eppserver.handler;
|
||||
|
||||
import static google.registry.eppserver.handler.EppProxyProtocolHandler.REMOTE_ADDRESS_KEY;
|
||||
import static google.registry.networking.handler.SslServerInitializer.CLIENT_CERTIFICATE_PROMISE_KEY;
|
||||
import static google.registry.util.GcpJsonFormatter.setCurrentRequest;
|
||||
import static google.registry.util.GcpJsonFormatter.setCurrentTraceId;
|
||||
import static google.registry.util.GcpJsonFormatter.unsetCurrentRequest;
|
||||
import static google.registry.util.X509Utils.getCertificateHash;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.eppserver.EppProtocolModule.CommandQuota;
|
||||
import google.registry.eppserver.metric.FrontendMetrics;
|
||||
import google.registry.eppserver.quota.LocalConnectionLimiter;
|
||||
import google.registry.eppserver.quota.QuotaManager;
|
||||
import google.registry.module.RegistryServlet;
|
||||
import google.registry.request.RequestHandler;
|
||||
import google.registry.util.FakeHttpServletRequest;
|
||||
import google.registry.util.FakeHttpServletResponse;
|
||||
import google.registry.util.ProxyHttpHeaders;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.util.AttributeKey;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Unified processor for EPP protocol traffic.
|
||||
*
|
||||
* <p>Consolidates throttling, session management, and in-process execution. Extracts registrar ID
|
||||
* (clID) directly from EPP login XML for accurate throttling.
|
||||
*/
|
||||
public class EppServiceHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
// Fast regex to extract clID from EPP login command without full XML parsing.
|
||||
private static final Pattern CLID_PATTERN =
|
||||
Pattern.compile("<clID>([^<]+)</clID>", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
public static final AttributeKey<String> CLIENT_CERTIFICATE_HASH_KEY =
|
||||
AttributeKey.valueOf("CLIENT_CERTIFICATE_HASH_KEY");
|
||||
|
||||
private final byte[] helloBytes;
|
||||
private final FrontendMetrics metrics;
|
||||
private final LocalConnectionLimiter localConnectionLimiter;
|
||||
private final QuotaManager commandQuotaManager;
|
||||
private final Supplier<String> idTokenSupplier;
|
||||
private final String projectId;
|
||||
|
||||
private String sslClientCertificateHash;
|
||||
private String clientAddress;
|
||||
private String registrarId; // The clID extracted from login
|
||||
private String sessionCookie;
|
||||
|
||||
private boolean ipAcquired = false;
|
||||
private boolean certAcquired = false;
|
||||
|
||||
@VisibleForTesting RequestHandler<?> requestHandler = RegistryServlet.component.requestHandler();
|
||||
|
||||
@Inject
|
||||
public EppServiceHandler(
|
||||
@Named("hello") byte[] helloBytes,
|
||||
FrontendMetrics metrics,
|
||||
LocalConnectionLimiter localConnectionLimiter,
|
||||
@CommandQuota QuotaManager commandQuotaManager,
|
||||
@Named("idToken") Supplier<String> idTokenSupplier,
|
||||
@Config("projectId") String projectId) {
|
||||
this.helloBytes = helloBytes.clone();
|
||||
this.metrics = metrics;
|
||||
this.localConnectionLimiter = localConnectionLimiter;
|
||||
this.commandQuotaManager = commandQuotaManager;
|
||||
this.idTokenSupplier = idTokenSupplier;
|
||||
this.projectId = projectId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
Promise<X509Certificate> certPromise = ctx.channel().attr(CLIENT_CERTIFICATE_PROMISE_KEY).get();
|
||||
if (certPromise != null) {
|
||||
certPromise.addListener(
|
||||
(Promise<X509Certificate> promise) -> {
|
||||
if (promise.isSuccess()) {
|
||||
ctx.executor().execute(() -> onSslHandshakeComplete(ctx, promise.getNow()));
|
||||
} else {
|
||||
logger.atWarning().withCause(promise.cause()).log("SSL handshake failed");
|
||||
@SuppressWarnings("unused")
|
||||
Future<?> unusedFuture = ctx.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
private void onSslHandshakeComplete(ChannelHandlerContext ctx, X509Certificate cert) {
|
||||
sslClientCertificateHash = getCertificateHash(cert);
|
||||
clientAddress = ctx.channel().attr(REMOTE_ADDRESS_KEY).get();
|
||||
ctx.channel().attr(CLIENT_CERTIFICATE_HASH_KEY).set(sslClientCertificateHash);
|
||||
|
||||
// 1. Connection throttling (IP and Certificate)
|
||||
if (!localConnectionLimiter.acquireIp(clientAddress)) {
|
||||
metrics.registerQuotaRejection("epp_connection_ip", clientAddress);
|
||||
@SuppressWarnings("unused")
|
||||
Future<?> unusedFuture = ctx.close();
|
||||
return;
|
||||
}
|
||||
ipAcquired = true;
|
||||
|
||||
if (!localConnectionLimiter.acquireCert(sslClientCertificateHash)) {
|
||||
metrics.registerQuotaRejection("epp_connection", sslClientCertificateHash);
|
||||
@SuppressWarnings("unused")
|
||||
Future<?> unusedFuture = ctx.close();
|
||||
return;
|
||||
}
|
||||
certAcquired = true;
|
||||
|
||||
metrics.registerActiveConnection("epp", sslClientCertificateHash, ctx.channel());
|
||||
|
||||
// 2. Trigger initial EPP <greeting>
|
||||
handleEppFrame(ctx, Unpooled.wrappedBuffer(helloBytes));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf frame) {
|
||||
handleEppFrame(ctx, frame);
|
||||
}
|
||||
|
||||
private void handleEppFrame(ChannelHandlerContext ctx, ByteBuf frame) {
|
||||
String xml = frame.toString(UTF_8);
|
||||
|
||||
// 1. Maturing Identity: If we don't have clID yet, try to extract it from a login command.
|
||||
if (registrarId == null) {
|
||||
Matcher matcher = CLID_PATTERN.matcher(xml);
|
||||
if (matcher.find()) {
|
||||
registrarId = matcher.group(1).trim();
|
||||
logger.atInfo().log("Identified registrar: %s", registrarId);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Command-level rate limiting
|
||||
// Use clID if identified, otherwise fallback to cert hash (for the login command itself).
|
||||
String throttleId = (registrarId != null) ? registrarId : sslClientCertificateHash;
|
||||
if (throttleId != null) {
|
||||
if (!commandQuotaManager.acquireQuota(new QuotaManager.QuotaRequest(throttleId)).success()) {
|
||||
metrics.registerQuotaRejection("epp_command", throttleId);
|
||||
@SuppressWarnings("unused")
|
||||
Future<?> unusedFuture = ctx.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Execute command in-process
|
||||
FakeHttpServletRequest req = new FakeHttpServletRequest();
|
||||
req.setRequestUri("/_dr/epp");
|
||||
req.setBody(xml.getBytes(UTF_8));
|
||||
req.setHeader(ProxyHttpHeaders.CERTIFICATE_HASH, sslClientCertificateHash);
|
||||
req.setHeader(ProxyHttpHeaders.IP_ADDRESS, clientAddress);
|
||||
if (registrarId != null) {
|
||||
req.setHeader(ProxyHttpHeaders.REGISTRAR_ID, registrarId);
|
||||
}
|
||||
if (sessionCookie != null) {
|
||||
req.setHeader("Cookie", sessionCookie);
|
||||
}
|
||||
req.setHeader("Authorization", "Bearer " + idTokenSupplier.get());
|
||||
|
||||
FakeHttpServletResponse rsp = new FakeHttpServletResponse();
|
||||
String traceId =
|
||||
String.format(
|
||||
"projects/%s/traces/%s", projectId, UUID.randomUUID().toString().replace("-", ""));
|
||||
setCurrentTraceId(traceId);
|
||||
setCurrentRequest("POST", "/_dr/epp", "Netty-EPP", "EPP/1.0");
|
||||
try {
|
||||
requestHandler.handleRequest(req, rsp);
|
||||
String setCookie = rsp.getHeader("Set-Cookie");
|
||||
if (setCookie != null) {
|
||||
sessionCookie = setCookie;
|
||||
}
|
||||
|
||||
ByteBuf out = Unpooled.wrappedBuffer(rsp.getPayload());
|
||||
if ("close".equals(rsp.getHeader(ProxyHttpHeaders.EPP_SESSION))) {
|
||||
@SuppressWarnings("unused")
|
||||
Future<?> unusedFuture = ctx.writeAndFlush(out).addListener(ChannelFutureListener.CLOSE);
|
||||
} else {
|
||||
@SuppressWarnings("unused")
|
||||
Future<?> unusedFuture = ctx.writeAndFlush(out);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.atSevere().withCause(e).log("Internal EPP processing error");
|
||||
@SuppressWarnings("unused")
|
||||
Future<?> unusedFuture = ctx.close();
|
||||
} finally {
|
||||
setCurrentTraceId(null);
|
||||
unsetCurrentRequest();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
if (certAcquired) {
|
||||
localConnectionLimiter.releaseCert(sslClientCertificateHash);
|
||||
}
|
||||
if (ipAcquired) {
|
||||
localConnectionLimiter.releaseIp(clientAddress);
|
||||
}
|
||||
super.channelInactive(ctx);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2017 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.eppserver.handler;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/** A handler that responds to GCP load balancer health check message */
|
||||
public class HealthCheckHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private final ByteBuf checkRequest;
|
||||
private final ByteBuf checkResponse;
|
||||
|
||||
public HealthCheckHandler(String checkRequest, String checkResponse) {
|
||||
this.checkRequest = Unpooled.wrappedBuffer(checkRequest.getBytes(StandardCharsets.US_ASCII));
|
||||
this.checkResponse = Unpooled.wrappedBuffer(checkResponse.getBytes(StandardCharsets.US_ASCII));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
ByteBuf buf = (ByteBuf) msg;
|
||||
if (buf.equals(checkRequest)) {
|
||||
ChannelFuture unusedFuture = ctx.writeAndFlush(checkResponse);
|
||||
}
|
||||
buf.release();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright 2019 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.eppserver.metric;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.monitoring.metrics.CustomFitter;
|
||||
import com.google.monitoring.metrics.ExponentialFitter;
|
||||
import com.google.monitoring.metrics.FibonacciFitter;
|
||||
import com.google.monitoring.metrics.LabelDescriptor;
|
||||
|
||||
/** Base class for metrics. */
|
||||
public abstract class BaseMetrics {
|
||||
|
||||
/**
|
||||
* Labels to register metrics with.
|
||||
*
|
||||
* <p>The client certificate hash value is only used for EPP metrics. In order to get the actual
|
||||
* registrar name, one can use the {@code nomulus} tool:
|
||||
*
|
||||
* <pre>
|
||||
* nomulus -e production list_registrars -f clientCertificateHash | grep $HASH
|
||||
* </pre>
|
||||
*/
|
||||
protected static final ImmutableSet<LabelDescriptor> LABELS =
|
||||
ImmutableSet.of(
|
||||
LabelDescriptor.create("protocol", "Name of the protocol."),
|
||||
LabelDescriptor.create(
|
||||
"client_cert_hash", "SHA256 hash of the client certificate, if available."));
|
||||
|
||||
// Maximum request size is defined in the config file, this is not realistic and we'd be out of
|
||||
// memory when the size approaches 1 GB.
|
||||
protected static final CustomFitter DEFAULT_SIZE_FITTER = FibonacciFitter.create(1073741824);
|
||||
|
||||
// Maximum 1 hour latency, this is not specified by the spec, but given we have a one hour idle
|
||||
// timeout, it seems reasonable that maximum latency is set to 1 hour as well. If we are
|
||||
// approaching anywhere near 1 hour latency, we'd be way out of SLO anyway.
|
||||
protected static final ExponentialFitter DEFAULT_LATENCY_FITTER =
|
||||
ExponentialFitter.create(22, 2, 1.0);
|
||||
|
||||
/**
|
||||
* Resets all metrics.
|
||||
*
|
||||
* <p>This should only be used in tests to reset states. Production code should not call this
|
||||
* method.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
abstract void resetMetrics();
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// Copyright 2017 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.eppserver.metric;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.monitoring.metrics.EventMetric;
|
||||
import com.google.monitoring.metrics.IncrementableMetric;
|
||||
import com.google.monitoring.metrics.Metric;
|
||||
import com.google.monitoring.metrics.MetricRegistryImpl;
|
||||
import google.registry.util.NonFinalForTesting;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.group.ChannelGroup;
|
||||
import io.netty.channel.group.DefaultChannelGroup;
|
||||
import io.netty.util.concurrent.GlobalEventExecutor;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/** Frontend metrics instrumentation. */
|
||||
@Singleton
|
||||
public class FrontendMetrics extends BaseMetrics {
|
||||
|
||||
private static final ConcurrentMap<ImmutableList<String>, ChannelGroup> activeConnections =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
static final Metric<Long> activeConnectionsGauge =
|
||||
MetricRegistryImpl.getDefault()
|
||||
.newGauge(
|
||||
"/eppserver/active_connections",
|
||||
"Number of active connections from clients to the EPP server.",
|
||||
"Active Connections",
|
||||
LABELS,
|
||||
() ->
|
||||
activeConnections.entrySet().stream()
|
||||
.collect(
|
||||
ImmutableMap.toImmutableMap(
|
||||
Map.Entry::getKey, entry -> (long) entry.getValue().size())),
|
||||
Long.class);
|
||||
|
||||
static final IncrementableMetric totalConnectionsCounter =
|
||||
MetricRegistryImpl.getDefault()
|
||||
.newIncrementableMetric(
|
||||
"/eppserver/total_connections",
|
||||
"Total number connections ever made from clients to the EPP server.",
|
||||
"Total Connections",
|
||||
LABELS);
|
||||
|
||||
static final IncrementableMetric quotaRejectionsCounter =
|
||||
MetricRegistryImpl.getDefault()
|
||||
.newIncrementableMetric(
|
||||
"/eppserver/quota_rejections",
|
||||
"Total number rejected quota request made by EPP server for each connection.",
|
||||
"Quota Rejections",
|
||||
LABELS);
|
||||
|
||||
static final EventMetric latencyMs =
|
||||
MetricRegistryImpl.getDefault()
|
||||
.newEventMetric(
|
||||
"/eppserver/latency_ms",
|
||||
"Round-trip time between a request received and its corresponding response is sent.",
|
||||
"Latency Milliseconds",
|
||||
LABELS,
|
||||
DEFAULT_LATENCY_FITTER);
|
||||
|
||||
private final Random random;
|
||||
private final double frontendMetricsRatio;
|
||||
|
||||
@Inject
|
||||
FrontendMetrics(@Named("frontendMetricsRatio") double frontendMetricsRatio, Random random) {
|
||||
this.frontendMetricsRatio = frontendMetricsRatio;
|
||||
this.random = random;
|
||||
}
|
||||
|
||||
@Override
|
||||
void resetMetrics() {
|
||||
totalConnectionsCounter.reset();
|
||||
activeConnections.clear();
|
||||
latencyMs.reset();
|
||||
}
|
||||
|
||||
@NonFinalForTesting
|
||||
public void registerActiveConnection(String protocol, String certHash, Channel channel) {
|
||||
totalConnectionsCounter.increment(protocol, certHash);
|
||||
ImmutableList<String> labels = ImmutableList.of(protocol, certHash);
|
||||
ChannelGroup channelGroup;
|
||||
if (activeConnections.containsKey(labels)) {
|
||||
channelGroup = activeConnections.get(labels);
|
||||
} else {
|
||||
channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
|
||||
activeConnections.put(labels, channelGroup);
|
||||
}
|
||||
channelGroup.add(channel);
|
||||
}
|
||||
|
||||
@NonFinalForTesting
|
||||
public void registerQuotaRejection(String protocol, String certHash) {
|
||||
quotaRejectionsCounter.increment(protocol, certHash);
|
||||
}
|
||||
|
||||
@NonFinalForTesting
|
||||
public void responseSent(String protocol, String certHash, Duration latency) {
|
||||
// Short-circuit metrics recording randomly according to the configured ratio.
|
||||
if (random.nextDouble() > frontendMetricsRatio) {
|
||||
return;
|
||||
}
|
||||
latencyMs.record(latency.toMillis(), protocol, certHash);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// Copyright 2026 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.eppserver.quota;
|
||||
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
/**
|
||||
* Thread-safe, in-memory rate limiter for restricting the number of concurrent connections allowed
|
||||
* per IP address and per authenticated certificate.
|
||||
*/
|
||||
@ThreadSafe
|
||||
@Singleton
|
||||
public class LocalConnectionLimiter {
|
||||
|
||||
private final int maxConnectionsPerIp;
|
||||
private final int maxConnectionsPerCert;
|
||||
|
||||
private final ConcurrentHashMap<String, Integer> ipConnections = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, Integer> certConnections = new ConcurrentHashMap<>();
|
||||
|
||||
@Inject
|
||||
public LocalConnectionLimiter(
|
||||
@Config("eppServerMaxConnectionsPerIp") int maxConnectionsPerIp,
|
||||
@Config("eppServerMaxConnectionsPerCert") int maxConnectionsPerCert) {
|
||||
this.maxConnectionsPerIp = maxConnectionsPerIp;
|
||||
this.maxConnectionsPerCert = maxConnectionsPerCert;
|
||||
}
|
||||
|
||||
/** Attempts to acquire a slot for the given IP address. */
|
||||
public boolean acquireIp(String ipAddress) {
|
||||
return acquire(ipAddress, ipConnections, maxConnectionsPerIp);
|
||||
}
|
||||
|
||||
/** Releases a slot for the given IP address. */
|
||||
public void releaseIp(String ipAddress) {
|
||||
release(ipAddress, ipConnections);
|
||||
}
|
||||
|
||||
/** Attempts to acquire a slot for the given certificate hash. */
|
||||
public boolean acquireCert(String certHash) {
|
||||
return acquire(certHash, certConnections, maxConnectionsPerCert);
|
||||
}
|
||||
|
||||
/** Releases a slot for the given certificate hash. */
|
||||
public void releaseCert(String certHash) {
|
||||
release(certHash, certConnections);
|
||||
}
|
||||
|
||||
private boolean acquire(String key, ConcurrentHashMap<String, Integer> map, int limit) {
|
||||
boolean[] acquired = new boolean[1];
|
||||
map.compute(
|
||||
key,
|
||||
(k, v) -> {
|
||||
if (v == null) {
|
||||
acquired[0] = true;
|
||||
return 1;
|
||||
}
|
||||
if (v < limit) {
|
||||
acquired[0] = true;
|
||||
return v + 1;
|
||||
}
|
||||
acquired[0] = false;
|
||||
return v;
|
||||
});
|
||||
return acquired[0];
|
||||
}
|
||||
|
||||
private void release(String key, ConcurrentHashMap<String, Integer> map) {
|
||||
map.computeIfPresent(
|
||||
key,
|
||||
(k, v) -> {
|
||||
if (v <= 1) {
|
||||
return null;
|
||||
}
|
||||
return v - 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
// Copyright 2024 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.eppserver.quota;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.config.RegistryConfigSettings.Quota;
|
||||
import google.registry.config.RegistryConfigSettings.Quota.QuotaGroup;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import redis.clients.jedis.UnifiedJedis;
|
||||
|
||||
/**
|
||||
* Unified manager for distributed quota enforcement using Redis/Valkey.
|
||||
*
|
||||
* <p>Handles both configuration lookup and atomic Redis operations for connection and command-level
|
||||
* throttling.
|
||||
*/
|
||||
@ThreadSafe
|
||||
public class QuotaManager {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
private static final int DEFAULT_TTL_SECONDS = 3600;
|
||||
|
||||
/** Lua script to atomically decrement a token bucket with a TTL. */
|
||||
private static final String DECR_LUA =
|
||||
"local current = redis.call('GET', KEYS[1]) "
|
||||
+ "if not current then "
|
||||
+ " redis.call('SET', KEYS[1], ARGV[1] - 1, 'EX', ARGV[2]) "
|
||||
+ " return tonumber(ARGV[1]) - 1 "
|
||||
+ "end "
|
||||
+ "if tonumber(current) <= 0 then "
|
||||
+ " return -1 "
|
||||
+ "end "
|
||||
+ "return redis.call('DECR', KEYS[1])";
|
||||
|
||||
/** Lua script to atomically increment back a connection token (capped at max). */
|
||||
private static final String INCR_LUA =
|
||||
"local current = redis.call('GET', KEYS[1]) "
|
||||
+ "if current and tonumber(current) < tonumber(ARGV[1]) then "
|
||||
+ " return redis.call('INCR', KEYS[1]) "
|
||||
+ "end "
|
||||
+ "return nil";
|
||||
|
||||
/** Lua script to refresh the TTL of an existing token bucket. */
|
||||
private static final String EXPIRE_LUA =
|
||||
"if redis.call('EXISTS', KEYS[1]) == 1 then "
|
||||
+ " return redis.call('EXPIRE', KEYS[1], ARGV[1]) "
|
||||
+ "end "
|
||||
+ "return 0";
|
||||
|
||||
private final UnifiedJedis jedis;
|
||||
private final String quotaNamespace;
|
||||
private final QuotaGroup defaultQuota;
|
||||
private final ImmutableMap<String, QuotaGroup> customQuotas;
|
||||
|
||||
public QuotaManager(Quota quota, @Nullable UnifiedJedis jedis, String quotaNamespace) {
|
||||
this.jedis = jedis;
|
||||
this.quotaNamespace = quotaNamespace;
|
||||
this.defaultQuota = quota.defaultQuota;
|
||||
|
||||
ImmutableMap.Builder<String, QuotaGroup> builder = ImmutableMap.builder();
|
||||
quota.customQuota.forEach(group -> group.userId.forEach(userId -> builder.put(userId, group)));
|
||||
this.customQuotas = builder.build();
|
||||
}
|
||||
|
||||
public record QuotaRequest(String userId) {}
|
||||
|
||||
public record QuotaResponse(boolean success) {}
|
||||
|
||||
public record QuotaRebate(String userId) {}
|
||||
|
||||
/** Attempts to acquire a quota token from Redis. */
|
||||
public QuotaResponse acquireQuota(QuotaRequest request) {
|
||||
String userId = request.userId();
|
||||
QuotaGroup group = customQuotas.getOrDefault(userId, defaultQuota);
|
||||
|
||||
// Unlimited quota check
|
||||
if (group.tokenAmount < 0) {
|
||||
return new QuotaResponse(true);
|
||||
}
|
||||
|
||||
if (jedis == null) {
|
||||
return new QuotaResponse(true); // Fail open if no Valkey configured
|
||||
}
|
||||
|
||||
// Use the first ID as the virtual group identity if it's a custom group,
|
||||
// otherwise isolate each default user by their actual ID.
|
||||
String redisId =
|
||||
(group == defaultQuota || group.userId.isEmpty()) ? userId : group.userId.get(0);
|
||||
String key = String.format("%s:%s", quotaNamespace, redisId);
|
||||
int ttl = group.refillSeconds > 0 ? group.refillSeconds : DEFAULT_TTL_SECONDS;
|
||||
|
||||
try {
|
||||
Object result =
|
||||
jedis.eval(DECR_LUA, 1, key, String.valueOf(group.tokenAmount), String.valueOf(ttl));
|
||||
|
||||
return new QuotaResponse(((Long) result) >= 0);
|
||||
} catch (Exception e) {
|
||||
logger.atSevere().withCause(e).log(
|
||||
"Valkey error for quota key: %s", URLEncoder.encode(key, StandardCharsets.UTF_8));
|
||||
return new QuotaResponse(true); // Fail open
|
||||
}
|
||||
}
|
||||
|
||||
/** Refreshes the TTL of an existing quota token. */
|
||||
public void refreshQuota(QuotaRequest request) {
|
||||
if (jedis == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String userId = request.userId();
|
||||
QuotaGroup group = customQuotas.getOrDefault(userId, defaultQuota);
|
||||
if (group.tokenAmount < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the first ID as the virtual group identity if it's a custom group,
|
||||
// otherwise isolate each default user by their actual ID.
|
||||
String redisId =
|
||||
(group == defaultQuota || group.userId.isEmpty()) ? userId : group.userId.get(0);
|
||||
String key = String.format("%s:%s", quotaNamespace, redisId);
|
||||
int ttl = group.refillSeconds > 0 ? group.refillSeconds : DEFAULT_TTL_SECONDS;
|
||||
|
||||
try {
|
||||
jedis.eval(EXPIRE_LUA, 1, key, String.valueOf(ttl));
|
||||
} catch (Exception e) {
|
||||
logger.atSevere().withCause(e).log(
|
||||
"Valkey error refreshing quota for: %s", URLEncoder.encode(key, StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a token to the pool (used for connection throttling). */
|
||||
public void releaseQuota(QuotaRebate rebate) {
|
||||
if (jedis == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String userId = rebate.userId();
|
||||
QuotaGroup group = customQuotas.getOrDefault(userId, defaultQuota);
|
||||
if (group.tokenAmount < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the first ID as the virtual group identity if it's a custom group,
|
||||
// otherwise isolate each default user by their actual ID.
|
||||
String redisId =
|
||||
(group == defaultQuota || group.userId.isEmpty()) ? userId : group.userId.get(0);
|
||||
String key = String.format("%s:%s", quotaNamespace, redisId);
|
||||
try {
|
||||
jedis.eval(INCR_LUA, 1, key, String.valueOf(group.tokenAmount));
|
||||
} catch (Exception e) {
|
||||
logger.atSevere().withCause(e).log(
|
||||
"Valkey error releasing quota for: %s", URLEncoder.encode(key, StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,13 +36,13 @@ import static google.registry.persistence.PersistenceModule.TransactionIsolation
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
|
||||
import static google.registry.pricing.PricingEngineProxy.isDomainPremium;
|
||||
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
|
||||
import static org.json.simple.JSONValue.toJSONString;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import com.google.common.net.MediaType;
|
||||
import com.google.gson.Gson;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.flows.domain.DomainFlowUtils.BadCommandForRegistryPhaseException;
|
||||
@@ -83,6 +83,7 @@ public class CheckApiAction implements Runnable {
|
||||
@Inject Response response;
|
||||
@Inject CheckApiMetric.Builder metricBuilder;
|
||||
@Inject CheckApiMetrics checkApiMetrics;
|
||||
@Inject Gson gson;
|
||||
|
||||
@Inject
|
||||
CheckApiAction() {}
|
||||
@@ -94,7 +95,7 @@ public class CheckApiAction implements Runnable {
|
||||
response.setHeader("X-Content-Type-Options", "nosniff");
|
||||
response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
|
||||
response.setContentType(MediaType.JSON_UTF_8);
|
||||
response.setPayload(toJSONString(doCheck()));
|
||||
response.setPayload(gson.toJson(doCheck()));
|
||||
} finally {
|
||||
CheckApiMetric metric = metricBuilder.build();
|
||||
checkApiMetrics.incrementCheckApiRequest(metric);
|
||||
|
||||
@@ -19,7 +19,6 @@ import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.io.BaseEncoding;
|
||||
import google.registry.request.Response;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
@@ -55,7 +54,6 @@ public class CookieSessionMetadata extends SessionMetadata {
|
||||
Pattern.compile("serviceExtensionUris=([^,\\s}]+)?");
|
||||
private static final Pattern FAILED_LOGIN_ATTEMPTS_PATTERN =
|
||||
Pattern.compile("failedLoginAttempts=([^,\\s]+)?");
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private final Map<String, String> data = new HashMap<>();
|
||||
|
||||
@@ -66,7 +64,6 @@ public class CookieSessionMetadata extends SessionMetadata {
|
||||
Matcher matcher = COOKIE_PATTERN.matcher(cookie);
|
||||
if (matcher.find()) {
|
||||
String sessionInfo = decode(matcher.group(1));
|
||||
logger.atInfo().log("SESSION INFO: %s", sessionInfo);
|
||||
matcher = REGISTRAR_ID_PATTERN.matcher(sessionInfo);
|
||||
if (matcher.find()) {
|
||||
String registrarId = matcher.group(1);
|
||||
|
||||
@@ -24,6 +24,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.flows.FlowModule.EppExceptionInProviderException;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.eppinput.EppInput;
|
||||
@@ -34,7 +35,6 @@ import google.registry.model.eppoutput.Result.Code;
|
||||
import google.registry.monitoring.whitebox.EppMetric;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import org.json.simple.JSONValue;
|
||||
|
||||
/**
|
||||
* An implementation of the EPP command/response protocol.
|
||||
@@ -50,6 +50,8 @@ public final class EppController {
|
||||
@Inject EppMetric.Builder eppMetricBuilder;
|
||||
@Inject EppMetrics eppMetrics;
|
||||
@Inject ServerTridProvider serverTridProvider;
|
||||
@Inject Gson gson;
|
||||
|
||||
@Inject EppController() {}
|
||||
|
||||
/** Reads EPP XML, executes the matching flow, and returns an {@link EppOutput}. */
|
||||
@@ -66,13 +68,16 @@ public final class EppController {
|
||||
try {
|
||||
eppInput = unmarshalEpp(EppInput.class, inputXmlBytes);
|
||||
} catch (EppException e) {
|
||||
// Log the unmarshalling error, with the raw bytes (in base64) to help with debugging.
|
||||
// Log the unmarshalling error, with the sanitized bytes (in base64) to help with debugging.
|
||||
Optional<String> sanitizedXml = EppXmlSanitizer.sanitizeEppXmlIfValid(inputXmlBytes);
|
||||
String xmlBytesToLog =
|
||||
base64().encode(sanitizedXml.map(xml -> xml.getBytes(UTF_8)).orElse(inputXmlBytes));
|
||||
logger.atInfo().withCause(e).log(
|
||||
"EPP request XML unmarshalling failed - \"%s\":\n%s\n%s\n%s\n%s",
|
||||
"EPP request XML unmarshalling failed - \"%s\":\n%s\n%s",
|
||||
e.getMessage(),
|
||||
lazy(
|
||||
() ->
|
||||
JSONValue.toJSONString(
|
||||
gson.toJson(
|
||||
ImmutableMap.<String, Object>of(
|
||||
"clientId",
|
||||
nullToEmpty(sessionMetadata.getRegistrarId()),
|
||||
@@ -81,12 +86,7 @@ public final class EppController {
|
||||
"resultMessage",
|
||||
e.getResult().getCode().msg,
|
||||
"xmlBytes",
|
||||
base64().encode(inputXmlBytes)))),
|
||||
LOG_SEPARATOR,
|
||||
lazy(
|
||||
() ->
|
||||
new String(inputXmlBytes, UTF_8)
|
||||
.trim()), // Charset decoding failures are swallowed.
|
||||
xmlBytesToLog))),
|
||||
LOG_SEPARATOR);
|
||||
// Return early by sending an error message, with no clTRID since we couldn't unmarshal it.
|
||||
eppMetricBuilder.setStatus(e.getResult().getCode());
|
||||
|
||||
@@ -87,16 +87,22 @@ public class EppXmlSanitizer {
|
||||
*
|
||||
* <p>Also, an empty element will be formatted as {@code <tag></tag>} instead of {@code <tag/>}.
|
||||
*/
|
||||
public static String sanitizeEppXml(byte[] inputXmlBytes) {
|
||||
public static Optional<String> sanitizeEppXmlIfValid(byte[] inputXmlBytes) {
|
||||
try {
|
||||
// Keep exactly one newline at end of sanitized string.
|
||||
return CharMatcher.whitespace().trimTrailingFrom(sanitizeAndEncode(inputXmlBytes)) + "\n";
|
||||
return Optional.of(
|
||||
CharMatcher.whitespace().trimTrailingFrom(sanitizeAndEncode(inputXmlBytes)) + "\n");
|
||||
} catch (XMLStreamException | UnsupportedEncodingException e) {
|
||||
logger.atWarning().withCause(e).log("Failed to sanitize EPP XML message.");
|
||||
return Base64.getMimeEncoder().encodeToString(inputXmlBytes);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public static String sanitizeEppXml(byte[] inputXmlBytes) {
|
||||
return sanitizeEppXmlIfValid(inputXmlBytes)
|
||||
.orElseGet(() -> Base64.getMimeEncoder().encodeToString(inputXmlBytes));
|
||||
}
|
||||
|
||||
private static String sanitizeAndEncode(byte[] inputXmlBytes)
|
||||
throws XMLStreamException, UnsupportedEncodingException {
|
||||
XMLEventReader xmlEventReader =
|
||||
|
||||
@@ -19,16 +19,6 @@ import dagger.Provides;
|
||||
import dagger.Subcomponent;
|
||||
import google.registry.batch.BatchModule;
|
||||
import google.registry.dns.DnsModule;
|
||||
import google.registry.flows.contact.ContactCheckFlow;
|
||||
import google.registry.flows.contact.ContactCreateFlow;
|
||||
import google.registry.flows.contact.ContactDeleteFlow;
|
||||
import google.registry.flows.contact.ContactInfoFlow;
|
||||
import google.registry.flows.contact.ContactTransferApproveFlow;
|
||||
import google.registry.flows.contact.ContactTransferCancelFlow;
|
||||
import google.registry.flows.contact.ContactTransferQueryFlow;
|
||||
import google.registry.flows.contact.ContactTransferRejectFlow;
|
||||
import google.registry.flows.contact.ContactTransferRequestFlow;
|
||||
import google.registry.flows.contact.ContactUpdateFlow;
|
||||
import google.registry.flows.custom.CustomLogicModule;
|
||||
import google.registry.flows.domain.DomainCheckFlow;
|
||||
import google.registry.flows.domain.DomainClaimsCheckFlow;
|
||||
@@ -54,6 +44,8 @@ import google.registry.flows.session.HelloFlow;
|
||||
import google.registry.flows.session.LoginFlow;
|
||||
import google.registry.flows.session.LogoutFlow;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/** Dagger component for flow classes. */
|
||||
@FlowScope
|
||||
@@ -69,16 +61,6 @@ public interface FlowComponent {
|
||||
FlowRunner flowRunner();
|
||||
|
||||
// Flows must be added here and in FlowComponentModule below.
|
||||
ContactCheckFlow contactCheckFlow();
|
||||
ContactCreateFlow contactCreateFlow();
|
||||
ContactDeleteFlow contactDeleteFlow();
|
||||
ContactInfoFlow contactInfoFlow();
|
||||
ContactTransferApproveFlow contactTransferApproveFlow();
|
||||
ContactTransferCancelFlow contactTransferCancelFlow();
|
||||
ContactTransferQueryFlow contactTransferQueryFlow();
|
||||
ContactTransferRejectFlow contactTransferRejectFlow();
|
||||
ContactTransferRequestFlow contactTransferRequestFlow();
|
||||
ContactUpdateFlow contactUpdateFlow();
|
||||
DomainCheckFlow domainCheckFlow();
|
||||
DomainClaimsCheckFlow domainClaimsCheckFlow();
|
||||
DomainCreateFlow domainCreateFlow();
|
||||
@@ -118,40 +100,16 @@ public interface FlowComponent {
|
||||
// TODO(b/29874464): fix this in a cleaner way.
|
||||
@Provides
|
||||
static Flow provideFlow(FlowComponent flows, Class<? extends Flow> clazz) {
|
||||
return clazz.equals(ContactCheckFlow.class) ? flows.contactCheckFlow()
|
||||
: clazz.equals(ContactCreateFlow.class) ? flows.contactCreateFlow()
|
||||
: clazz.equals(ContactDeleteFlow.class) ? flows.contactDeleteFlow()
|
||||
: clazz.equals(ContactInfoFlow.class) ? flows.contactInfoFlow()
|
||||
: clazz.equals(ContactTransferApproveFlow.class) ? flows.contactTransferApproveFlow()
|
||||
: clazz.equals(ContactTransferCancelFlow.class) ? flows.contactTransferCancelFlow()
|
||||
: clazz.equals(ContactTransferQueryFlow.class) ? flows.contactTransferQueryFlow()
|
||||
: clazz.equals(ContactTransferRejectFlow.class) ? flows.contactTransferRejectFlow()
|
||||
: clazz.equals(ContactTransferRequestFlow.class) ? flows.contactTransferRequestFlow()
|
||||
: clazz.equals(ContactUpdateFlow.class) ? flows.contactUpdateFlow()
|
||||
: clazz.equals(DomainCheckFlow.class) ? flows.domainCheckFlow()
|
||||
: clazz.equals(DomainClaimsCheckFlow.class) ? flows.domainClaimsCheckFlow()
|
||||
: clazz.equals(DomainCreateFlow.class) ? flows.domainCreateFlow()
|
||||
: clazz.equals(DomainDeleteFlow.class) ? flows.domainDeleteFlow()
|
||||
: clazz.equals(DomainInfoFlow.class) ? flows.domainInfoFlow()
|
||||
: clazz.equals(DomainRenewFlow.class) ? flows.domainRenewFlow()
|
||||
: clazz.equals(DomainRestoreRequestFlow.class) ? flows.domainRestoreRequestFlow()
|
||||
: clazz.equals(DomainTransferApproveFlow.class) ? flows.domainTransferApproveFlow()
|
||||
: clazz.equals(DomainTransferCancelFlow.class) ? flows.domainTransferCancelFlow()
|
||||
: clazz.equals(DomainTransferQueryFlow.class) ? flows.domainTransferQueryFlow()
|
||||
: clazz.equals(DomainTransferRejectFlow.class) ? flows.domainTransferRejectFlow()
|
||||
: clazz.equals(DomainTransferRequestFlow.class) ? flows.domainTransferRequestFlow()
|
||||
: clazz.equals(DomainUpdateFlow.class) ? flows.domainUpdateFlow()
|
||||
: clazz.equals(HostCheckFlow.class) ? flows.hostCheckFlow()
|
||||
: clazz.equals(HostCreateFlow.class) ? flows.hostCreateFlow()
|
||||
: clazz.equals(HostDeleteFlow.class) ? flows.hostDeleteFlow()
|
||||
: clazz.equals(HostInfoFlow.class) ? flows.hostInfoFlow()
|
||||
: clazz.equals(HostUpdateFlow.class) ? flows.hostUpdateFlow()
|
||||
: clazz.equals(PollAckFlow.class) ? flows.pollAckFlow()
|
||||
: clazz.equals(PollRequestFlow.class) ? flows.pollRequestFlow()
|
||||
: clazz.equals(HelloFlow.class) ? flows.helloFlow()
|
||||
: clazz.equals(LoginFlow.class) ? flows.loginFlow()
|
||||
: clazz.equals(LogoutFlow.class) ? flows.logoutFlow()
|
||||
: null;
|
||||
String simpleName = clazz.getSimpleName();
|
||||
// The method name is the same as the class name but with the first character being lowercase
|
||||
String methodName = Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1);
|
||||
try {
|
||||
Method method = FlowComponent.class.getMethod(methodName);
|
||||
method.setAccessible(true);
|
||||
return (Flow) method.invoke(flows);
|
||||
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,14 +15,15 @@
|
||||
package google.registry.flows;
|
||||
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.toLogSafeLabel;
|
||||
import static java.util.Collections.EMPTY_LIST;
|
||||
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.flows.FlowModule.InputXml;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
@@ -30,7 +31,6 @@ import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.eppinput.EppInput;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import org.json.simple.JSONValue;
|
||||
|
||||
/** Reporter used by {@link FlowRunner} to record flow execution data for reporting. */
|
||||
public class FlowReporter {
|
||||
@@ -49,6 +49,8 @@ public class FlowReporter {
|
||||
@Inject @InputXml byte[] inputXmlBytes;
|
||||
@Inject EppInput eppInput;
|
||||
@Inject Class<? extends Flow> flowClass;
|
||||
@Inject Gson gson;
|
||||
|
||||
@Inject FlowReporter() {}
|
||||
|
||||
/** Records information about the current flow execution in the request logs. */
|
||||
@@ -61,7 +63,7 @@ public class FlowReporter {
|
||||
logger.atInfo().log(
|
||||
"%s: %s",
|
||||
METADATA_LOG_SIGNATURE,
|
||||
JSONValue.toJSONString(
|
||||
gson.toJson(
|
||||
new ImmutableMap.Builder<String, Object>()
|
||||
.put("serverTrid", trid.getServerTransactionId())
|
||||
.put("clientId", registrarId)
|
||||
@@ -88,9 +90,7 @@ public class FlowReporter {
|
||||
*/
|
||||
private static Optional<String> extractTld(String domainName) {
|
||||
int index = domainName.indexOf('.');
|
||||
return index == -1
|
||||
? Optional.empty()
|
||||
: Optional.of(Ascii.toLowerCase(domainName.substring(index + 1)));
|
||||
return index == -1 ? Optional.empty() : toLogSafeLabel(domainName.substring(index + 1));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -44,6 +44,9 @@ import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@@ -98,8 +101,30 @@ public final class ResourceFlowUtils {
|
||||
|
||||
public static <R extends EppResource> void verifyResourceDoesNotExist(
|
||||
Class<R> clazz, String targetId, Instant now, String registrarId) throws EppException {
|
||||
Optional<R> resource = ForeignKeyUtils.loadResource(clazz, targetId, now);
|
||||
verifyResourceDoesNotExist(clazz, targetId, now, Duration.ZERO, registrarId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a resource with the specified type and id did not exist at the specified time.
|
||||
*
|
||||
* <p>This will throw an exception if the resource exists with a deletionTime greater than the
|
||||
* specified time (i.e. a registrar is attempting to create a duplicate domain, host, or contact).
|
||||
* If the resource had existed within the specified lookback window, but is not active now, i.e.
|
||||
* it is recently deleted, then don't throw an exception, but do return the recently deleted
|
||||
* resource for further processing (this is used by the Expiry Access Period).
|
||||
*
|
||||
* <p>If there is no need to specially handle the situation of recently deleted resources, then
|
||||
* simply pass a lookback duration of {@link Duration#ZERO}.
|
||||
*/
|
||||
public static <R extends EppResource> Optional<R> verifyResourceDoesNotExist(
|
||||
Class<R> clazz, String targetId, Instant now, Duration lookback, String registrarId)
|
||||
throws EppException {
|
||||
Instant startOfWindow = now.minus(lookback);
|
||||
Optional<R> resource = ForeignKeyUtils.loadResource(clazz, targetId, startOfWindow);
|
||||
if (resource.isPresent()) {
|
||||
if (resource.get().getDeletionTime().isBefore(now)) {
|
||||
return resource;
|
||||
}
|
||||
// These are similar exceptions, but we can track them internally as log-based metrics.
|
||||
if (Objects.equals(registrarId, resource.get().getPersistedCurrentSponsorRegistrarId())) {
|
||||
throw new ResourceAlreadyExistsForThisClientException(targetId);
|
||||
@@ -107,6 +132,7 @@ public final class ResourceFlowUtils {
|
||||
throw new ResourceCreateContentionException(targetId);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/** Check that the given AuthInfo is present for a resource being transferred. */
|
||||
@@ -134,7 +160,9 @@ public final class ResourceFlowUtils {
|
||||
}
|
||||
String authPassword = authInfo.getPw().getValue();
|
||||
String domainPassword = domain.getAuthInfo().getPw().getValue();
|
||||
if (!domainPassword.equals(authPassword)) {
|
||||
if (!MessageDigest.isEqual(
|
||||
authPassword.getBytes(StandardCharsets.UTF_8),
|
||||
domainPassword.getBytes(StandardCharsets.UTF_8))) {
|
||||
throw new BadAuthInfoForResourceException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +260,7 @@ public class TlsCredentials implements TransportCredentials {
|
||||
static Optional<InetAddress> provideIpAddress(HttpServletRequest req) {
|
||||
Optional<String> clientAddress = extractOptionalHeader(req, ProxyHttpHeaders.IP_ADDRESS);
|
||||
Optional<String> fallbackClientAddress =
|
||||
extractOptionalHeader(req, ProxyHttpHeaders.IP_ADDRESS);
|
||||
extractOptionalHeader(req, ProxyHttpHeaders.FALLBACK_IP_ADDRESS);
|
||||
Optional<InetAddress> clientInetAddr = clientAddress.map(TlsCredentials::parseInetAddress);
|
||||
return clientInetAddr.isPresent()
|
||||
? clientInetAddr
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright 2017 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.flows.contact;
|
||||
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
/**
|
||||
* An EPP flow that is meant to check whether a contact can be provisioned.
|
||||
*
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_CHECK)
|
||||
public final class ContactCheckFlow extends ContactsProhibitedFlow {
|
||||
@Inject ContactCheckFlow() {}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright 2017 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.flows.contact;
|
||||
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
/**
|
||||
* An EPP flow meant to create a new contact.
|
||||
*
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_CREATE)
|
||||
public final class ContactCreateFlow extends ContactsProhibitedFlow {
|
||||
@Inject ContactCreateFlow() {}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright 2017 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.flows.contact;
|
||||
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
/**
|
||||
* An EPP flow that is meant to delete a contact.
|
||||
*
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_DELETE)
|
||||
public final class ContactDeleteFlow extends ContactsProhibitedFlow {
|
||||
@Inject
|
||||
ContactDeleteFlow() {}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright 2017 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.flows.contact;
|
||||
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
/**
|
||||
* An EPP flow that is meant to return information about a contact.
|
||||
*
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_INFO)
|
||||
public final class ContactInfoFlow extends ContactsProhibitedFlow {
|
||||
@Inject
|
||||
ContactInfoFlow() {}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright 2017 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.flows.contact;
|
||||
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
/**
|
||||
* An EPP flow that is meant to approve a pending transfer on a contact.
|
||||
*
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_APPROVE)
|
||||
public final class ContactTransferApproveFlow extends ContactsProhibitedFlow {
|
||||
@Inject ContactTransferApproveFlow() {}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright 2017 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.flows.contact;
|
||||
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
/**
|
||||
* An EPP flow that is meant to cancel a pending transfer on a contact.
|
||||
*
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_CANCEL)
|
||||
public final class ContactTransferCancelFlow extends ContactsProhibitedFlow {
|
||||
@Inject ContactTransferCancelFlow() {}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright 2017 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.flows.contact;
|
||||
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
/**
|
||||
* An EPP flow that is meant to query a pending transfer on a contact.
|
||||
*
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_QUERY)
|
||||
public final class ContactTransferQueryFlow extends ContactsProhibitedFlow {
|
||||
@Inject ContactTransferQueryFlow() {}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright 2017 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.flows.contact;
|
||||
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
/**
|
||||
* An EPP flow that is meant to reject a pending transfer on a contact.
|
||||
*
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_REJECT)
|
||||
public final class ContactTransferRejectFlow extends ContactsProhibitedFlow {
|
||||
@Inject ContactTransferRejectFlow() {}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright 2017 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.flows.contact;
|
||||
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
/**
|
||||
* An EPP flow that is meant to request a transfer on a contact.
|
||||
*
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_REQUEST)
|
||||
public final class ContactTransferRequestFlow extends ContactsProhibitedFlow {
|
||||
@Inject
|
||||
ContactTransferRequestFlow() {}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright 2017 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.flows.contact;
|
||||
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
/**
|
||||
* An EPP flow meant to update a contact.
|
||||
*
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_UPDATE)
|
||||
public final class ContactUpdateFlow extends ContactsProhibitedFlow {
|
||||
@Inject ContactUpdateFlow() {}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.Flow;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
|
||||
/** Nomulus follows the Minimum Dataset Requirements, meaning it stores no contact information. */
|
||||
public abstract class ContactsProhibitedFlow implements Flow {
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
throw new ContactsProhibitedException();
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import static google.registry.flows.domain.DomainFlowUtils.checkHasBillingAccoun
|
||||
import static google.registry.flows.domain.DomainFlowUtils.getReservationTypes;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.handleFeeRequest;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.isAnchorTenant;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.isDomainEligibleForXap;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.isRegisterBsaCreate;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.isReserved;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.isValidReservedCreate;
|
||||
@@ -75,6 +76,7 @@ import google.registry.model.eppoutput.CheckData.DomainCheck;
|
||||
import google.registry.model.eppoutput.CheckData.DomainCheckData;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.model.tld.Tld.TldState;
|
||||
@@ -82,6 +84,7 @@ import google.registry.model.tld.label.ReservationType;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.util.Clock;
|
||||
import jakarta.inject.Inject;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
@@ -138,6 +141,10 @@ public final class DomainCheckFlow implements TransactionalFlow {
|
||||
@Config("maxChecks")
|
||||
int maxChecks;
|
||||
|
||||
@Inject
|
||||
@Config("domainExpiryAccessPeriodTotalLength")
|
||||
Duration domainExpiryAccessPeriodTotalLength;
|
||||
|
||||
@Inject @Superuser boolean isSuperuser;
|
||||
@Inject Clock clock;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
@@ -249,7 +256,14 @@ public final class DomainCheckFlow implements TransactionalFlow {
|
||||
return Optional.of(e.getMessage());
|
||||
}
|
||||
return getMessageForCheckWithToken(
|
||||
idn, existingDomains, bsaBlockedDomainNames, tldStates, token);
|
||||
idn, existingDomains, bsaBlockedDomainNames, tldStates, token, now);
|
||||
}
|
||||
|
||||
private static Optional<Domain> loadDomainIfInXap(
|
||||
String domainName, Instant now, Duration domainExpiryAccessPeriodTotalLength) {
|
||||
return ForeignKeyUtils.loadResource(
|
||||
Domain.class, domainName, now.minus(domainExpiryAccessPeriodTotalLength))
|
||||
.filter(domain -> isDomainEligibleForXap(domain, Tld.get(domain.getTld()), now));
|
||||
}
|
||||
|
||||
private Optional<String> getMessageForCheckWithToken(
|
||||
@@ -257,10 +271,18 @@ public final class DomainCheckFlow implements TransactionalFlow {
|
||||
ImmutableMap<String, VKey<Domain>> existingDomains,
|
||||
ImmutableSet<InternetDomainName> bsaBlockedDomains,
|
||||
ImmutableMap<String, TldState> tldStates,
|
||||
Optional<AllocationToken> allocationToken) {
|
||||
Optional<AllocationToken> allocationToken,
|
||||
Instant now) {
|
||||
if (existingDomains.containsKey(domainName.toString())) {
|
||||
return Optional.of("In use");
|
||||
}
|
||||
Tld tld = Tld.get(domainName.parent().toString());
|
||||
if (tld.getExpiryAccessPeriodModeAt(now) == Tld.ExpiryAccessPeriodMode.ENABLED
|
||||
&& !Registrar.loadByRegistrarIdCached(registrarId).get().getExpiryAccessPeriodEnabled()
|
||||
&& loadDomainIfInXap(domainName.toString(), now, domainExpiryAccessPeriodTotalLength)
|
||||
.isPresent()) {
|
||||
return Optional.of("Reserved");
|
||||
}
|
||||
TldState tldState = tldStates.get(domainName.parent().toString());
|
||||
if (isReserved(domainName, START_DATE_SUNRISE.equals(tldState))) {
|
||||
if (!isValidReservedCreate(domainName, allocationToken)
|
||||
@@ -339,16 +361,27 @@ public final class DomainCheckFlow implements TransactionalFlow {
|
||||
.build());
|
||||
continue;
|
||||
}
|
||||
Optional<Domain> domainForFee = domain;
|
||||
boolean isAvailable = availableDomains.contains(domainName);
|
||||
if (isAvailable
|
||||
&& feeCheckItem.getCommandName().equals(FeeQueryCommandExtensionItem.CommandName.CREATE)
|
||||
&& tld.getExpiryAccessPeriodModeAt(now) == Tld.ExpiryAccessPeriodMode.ENABLED) {
|
||||
Optional<Domain> recentlyDeletedDomain =
|
||||
loadDomainIfInXap(domainName, now, domainExpiryAccessPeriodTotalLength);
|
||||
if (recentlyDeletedDomain.isPresent()) {
|
||||
domainForFee = recentlyDeletedDomain;
|
||||
}
|
||||
}
|
||||
handleFeeRequest(
|
||||
feeCheckItem,
|
||||
builder,
|
||||
domainNames.get(domainName),
|
||||
domain,
|
||||
domainForFee,
|
||||
feeCheck.getCurrency(),
|
||||
now,
|
||||
pricingLogic,
|
||||
token,
|
||||
availableDomains.contains(domainName),
|
||||
isAvailable,
|
||||
recurrences.getOrDefault(domainName, null));
|
||||
// In the case of a registrar that is running a tiered pricing promotion, we issue two
|
||||
// responses for the CREATE fee check command: one (the default response) with the
|
||||
|
||||
@@ -18,6 +18,7 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.dns.DnsUtils.requestDomainDnsRefresh;
|
||||
import static google.registry.flows.FlowUtils.persistEntityChanges;
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyResourceDoesNotExist;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.COLLISION_MESSAGE;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.checkHasBillingAccount;
|
||||
@@ -25,6 +26,7 @@ import static google.registry.flows.domain.DomainFlowUtils.cloneAndLinkReference
|
||||
import static google.registry.flows.domain.DomainFlowUtils.createFeeCreateResponse;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.getReservationTypes;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.isAnchorTenant;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.isDomainEligibleForXap;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.isReserved;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.isValidReservedCreate;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.validateCreateCommandContactsAndNameservers;
|
||||
@@ -58,6 +60,7 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import google.registry.config.RegistryConfig;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.EppException.CommandUseErrorException;
|
||||
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||
@@ -71,6 +74,7 @@ import google.registry.flows.custom.DomainCreateFlowCustomLogic;
|
||||
import google.registry.flows.custom.DomainCreateFlowCustomLogic.BeforeResponseParameters;
|
||||
import google.registry.flows.custom.DomainCreateFlowCustomLogic.BeforeResponseReturnData;
|
||||
import google.registry.flows.custom.EntityChanges;
|
||||
import google.registry.flows.domain.DomainFlowUtils.DomainReservedException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.RegistrantProhibitedException;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
@@ -107,6 +111,7 @@ import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.poll.PollMessage.Autorenew;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.reporting.DomainTransactionRecord;
|
||||
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
@@ -123,6 +128,7 @@ import jakarta.inject.Inject;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
import org.joda.money.Money;
|
||||
|
||||
/**
|
||||
* An EPP flow that creates a new domain resource.
|
||||
@@ -219,6 +225,10 @@ public final class DomainCreateFlow implements MutatingFlow {
|
||||
@Inject DomainPricingLogic pricingLogic;
|
||||
@Inject DomainDeletionTimeCache domainDeletionTimeCache;
|
||||
|
||||
@Inject
|
||||
@Config("domainExpiryAccessPeriodTotalLength")
|
||||
Duration domainExpiryAccessPeriodTotalLength;
|
||||
|
||||
@Inject
|
||||
DomainCreateFlow() {}
|
||||
|
||||
@@ -245,6 +255,13 @@ public final class DomainCreateFlow implements MutatingFlow {
|
||||
InternetDomainName domainName = validateDomainName(command.getDomainName());
|
||||
String domainLabel = domainName.parts().getFirst();
|
||||
Tld tld = Tld.get(domainName.parent().toString());
|
||||
Duration lookback =
|
||||
tld.getExpiryAccessPeriodModeAt(now) == Tld.ExpiryAccessPeriodMode.ENABLED
|
||||
? domainExpiryAccessPeriodTotalLength
|
||||
: Duration.ZERO;
|
||||
Optional<Domain> recentlyDeletedDomain =
|
||||
verifyResourceDoesNotExist(Domain.class, targetId, now, lookback, registrarId)
|
||||
.filter(domain -> isDomainEligibleForXap(domain, tld, now));
|
||||
validateCreateCommandContactsAndNameservers(command, tld, domainName);
|
||||
TldState tldState = tld.getTldState(now);
|
||||
Optional<LaunchCreateExtension> launchCreate =
|
||||
@@ -280,6 +297,10 @@ public final class DomainCreateFlow implements MutatingFlow {
|
||||
if (!isSuperuser) {
|
||||
checkAllowedAccessToTld(registrarId, tld.getTldStr());
|
||||
checkHasBillingAccount(registrarId, tld.getTldStr());
|
||||
if (recentlyDeletedDomain.isPresent()
|
||||
&& !Registrar.loadByRegistrarIdCached(registrarId).get().getExpiryAccessPeriodEnabled()) {
|
||||
throw new DomainReservedException(domainName.toString());
|
||||
}
|
||||
boolean isValidReservedCreate = isValidReservedCreate(domainName, allocationToken);
|
||||
ClaimsList claimsList = ClaimsListDao.get();
|
||||
verifyIsGaOrSpecialCase(
|
||||
@@ -327,7 +348,14 @@ public final class DomainCreateFlow implements MutatingFlow {
|
||||
eppInput.getSingleExtension(FeeCreateCommandExtension.class);
|
||||
FeesAndCredits feesAndCredits =
|
||||
pricingLogic.getCreatePrice(
|
||||
tld, targetId, now, years, isAnchorTenant, isSunriseCreate, allocationToken);
|
||||
tld,
|
||||
targetId,
|
||||
now,
|
||||
recentlyDeletedDomain,
|
||||
years,
|
||||
isAnchorTenant,
|
||||
isSunriseCreate,
|
||||
allocationToken);
|
||||
validateFeeChallenge(feeCreate, feesAndCredits, defaultTokenUsed);
|
||||
Optional<SecDnsCreateExtension> secDnsCreate =
|
||||
validateSecDnsExtension(eppInput.getSingleExtension(SecDnsCreateExtension.class));
|
||||
@@ -358,7 +386,15 @@ public final class DomainCreateFlow implements MutatingFlow {
|
||||
entitiesToInsert.add(createBillingEvent, autorenewBillingEvent, autorenewPollMessage);
|
||||
// Bill for EAP cost, if any.
|
||||
if (!feesAndCredits.getEapCost().isZero()) {
|
||||
entitiesToInsert.add(createEapBillingEvent(feesAndCredits, createBillingEvent));
|
||||
entitiesToInsert.add(
|
||||
createAdditionalBillingEvent(
|
||||
Reason.FEE_EARLY_ACCESS, feesAndCredits.getEapCost(), createBillingEvent));
|
||||
}
|
||||
// Bill for XAP cost, if any.
|
||||
if (!feesAndCredits.getXapCost().isZero()) {
|
||||
entitiesToInsert.add(
|
||||
createAdditionalBillingEvent(
|
||||
Reason.FEE_EXPIRY_ACCESS, feesAndCredits.getXapCost(), createBillingEvent));
|
||||
}
|
||||
|
||||
ImmutableSet<ReservationType> reservationTypes = getReservationTypes(domainName);
|
||||
@@ -434,7 +470,14 @@ public final class DomainCreateFlow implements MutatingFlow {
|
||||
FeesAndCredits responseFeesAndCredits =
|
||||
shouldShowDefaultPrice
|
||||
? pricingLogic.getCreatePrice(
|
||||
tld, targetId, now, years, isAnchorTenant, isSunriseCreate, Optional.empty())
|
||||
tld,
|
||||
targetId,
|
||||
now,
|
||||
recentlyDeletedDomain,
|
||||
years,
|
||||
isAnchorTenant,
|
||||
isSunriseCreate,
|
||||
Optional.empty())
|
||||
: feesAndCredits;
|
||||
|
||||
BeforeResponseReturnData responseData =
|
||||
@@ -656,14 +699,14 @@ public final class DomainCreateFlow implements MutatingFlow {
|
||||
}
|
||||
}
|
||||
|
||||
private static BillingEvent createEapBillingEvent(
|
||||
FeesAndCredits feesAndCredits, BillingEvent createBillingEvent) {
|
||||
private static BillingEvent createAdditionalBillingEvent(
|
||||
Reason reason, Money cost, BillingEvent createBillingEvent) {
|
||||
return new BillingEvent.Builder()
|
||||
.setReason(Reason.FEE_EARLY_ACCESS)
|
||||
.setReason(reason)
|
||||
.setTargetId(createBillingEvent.getTargetId())
|
||||
.setRegistrarId(createBillingEvent.getRegistrarId())
|
||||
.setPeriodYears(1)
|
||||
.setCost(feesAndCredits.getEapCost())
|
||||
.setCost(cost)
|
||||
.setEventTime(createBillingEvent.getEventTime())
|
||||
.setBillingTime(createBillingEvent.getBillingTime())
|
||||
.setFlags(createBillingEvent.getFlags())
|
||||
|
||||
@@ -57,6 +57,9 @@ public final class DomainFlowTmchUtils {
|
||||
public SignedMark verifySignedMarks(
|
||||
ImmutableList<AbstractSignedMark> signedMarks, String domainLabel, Instant now)
|
||||
throws EppException {
|
||||
if (signedMarks.isEmpty()) {
|
||||
throw new SignedMarksListEmptyException();
|
||||
}
|
||||
if (signedMarks.size() > 1) {
|
||||
throw new TooManySignedMarksException();
|
||||
}
|
||||
@@ -77,21 +80,21 @@ public final class DomainFlowTmchUtils {
|
||||
|
||||
public SignedMark verifyEncodedSignedMark(EncodedSignedMark encodedSignedMark, Instant now)
|
||||
throws EppException {
|
||||
if (!encodedSignedMark.getEncoding().equals("base64")) {
|
||||
if (!"base64".equals(encodedSignedMark.getEncoding())) {
|
||||
throw new Base64RequiredForEncodedSignedMarksException();
|
||||
}
|
||||
byte[] signedMarkData;
|
||||
try {
|
||||
signedMarkData = encodedSignedMark.getBytes();
|
||||
} catch (IllegalStateException e) {
|
||||
throw new SignedMarkEncodingErrorException();
|
||||
throw new SignedMarkEncodingErrorException(e);
|
||||
}
|
||||
|
||||
SignedMark signedMark;
|
||||
try {
|
||||
signedMark = unmarshalEpp(SignedMark.class, signedMarkData);
|
||||
} catch (EppException e) {
|
||||
throw new SignedMarkParsingErrorException();
|
||||
throw new SignedMarkParsingErrorException(e);
|
||||
}
|
||||
|
||||
if (SignedMarkRevocationList.get().isSmdRevoked(signedMark.getId(), now)) {
|
||||
@@ -101,22 +104,22 @@ public final class DomainFlowTmchUtils {
|
||||
try {
|
||||
tmchXmlSignature.verify(signedMarkData);
|
||||
} catch (CertificateExpiredException e) {
|
||||
throw new SignedMarkCertificateExpiredException();
|
||||
throw new SignedMarkCertificateExpiredException(e);
|
||||
} catch (CertificateNotYetValidException e) {
|
||||
throw new SignedMarkCertificateNotYetValidException();
|
||||
throw new SignedMarkCertificateNotYetValidException(e);
|
||||
} catch (CertificateRevokedException e) {
|
||||
throw new SignedMarkCertificateRevokedException();
|
||||
throw new SignedMarkCertificateRevokedException(e);
|
||||
} catch (CertificateSignatureException e) {
|
||||
throw new SignedMarkCertificateSignatureException();
|
||||
throw new SignedMarkCertificateSignatureException(e);
|
||||
} catch (SignatureException | XMLSignatureException e) {
|
||||
throw new SignedMarkSignatureException();
|
||||
throw new SignedMarkSignatureException(e);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new SignedMarkCertificateInvalidException();
|
||||
throw new SignedMarkCertificateInvalidException(e);
|
||||
} catch (IOException
|
||||
| MarshalException
|
||||
| SAXException
|
||||
| ParserConfigurationException e) {
|
||||
throw new SignedMarkParsingErrorException();
|
||||
throw new SignedMarkParsingErrorException(e);
|
||||
}
|
||||
|
||||
if (now.isBefore(signedMark.getCreationTime())) {
|
||||
@@ -181,6 +184,11 @@ public final class DomainFlowTmchUtils {
|
||||
public SignedMarkCertificateRevokedException() {
|
||||
super("Signed mark certificate was revoked");
|
||||
}
|
||||
|
||||
public SignedMarkCertificateRevokedException(Throwable cause) {
|
||||
this();
|
||||
initCause(cause);
|
||||
}
|
||||
}
|
||||
|
||||
/** Certificate used in signed mark signature has expired. */
|
||||
@@ -189,6 +197,11 @@ public final class DomainFlowTmchUtils {
|
||||
public SignedMarkCertificateNotYetValidException() {
|
||||
super("Signed mark certificate not yet valid");
|
||||
}
|
||||
|
||||
public SignedMarkCertificateNotYetValidException(Throwable cause) {
|
||||
this();
|
||||
initCause(cause);
|
||||
}
|
||||
}
|
||||
|
||||
/** Certificate used in signed mark signature has expired. */
|
||||
@@ -196,6 +209,11 @@ public final class DomainFlowTmchUtils {
|
||||
public SignedMarkCertificateExpiredException() {
|
||||
super("Signed mark certificate has expired");
|
||||
}
|
||||
|
||||
public SignedMarkCertificateExpiredException(Throwable cause) {
|
||||
this();
|
||||
initCause(cause);
|
||||
}
|
||||
}
|
||||
|
||||
/** Certificate parsing error, or possibly a bad provider or algorithm. */
|
||||
@@ -203,6 +221,11 @@ public final class DomainFlowTmchUtils {
|
||||
public SignedMarkCertificateInvalidException() {
|
||||
super("Signed mark certificate is invalid");
|
||||
}
|
||||
|
||||
public SignedMarkCertificateInvalidException(Throwable cause) {
|
||||
this();
|
||||
initCause(cause);
|
||||
}
|
||||
}
|
||||
|
||||
/** Invalid signature on a signed mark. */
|
||||
@@ -210,6 +233,11 @@ public final class DomainFlowTmchUtils {
|
||||
public SignedMarkCertificateSignatureException() {
|
||||
super("Signed mark certificate not signed by ICANN");
|
||||
}
|
||||
|
||||
public SignedMarkCertificateSignatureException(Throwable cause) {
|
||||
this();
|
||||
initCause(cause);
|
||||
}
|
||||
}
|
||||
|
||||
/** Invalid signature on a signed mark. */
|
||||
@@ -217,6 +245,11 @@ public final class DomainFlowTmchUtils {
|
||||
public SignedMarkSignatureException() {
|
||||
super("Signed mark signature is invalid");
|
||||
}
|
||||
|
||||
public SignedMarkSignatureException(Throwable cause) {
|
||||
this();
|
||||
initCause(cause);
|
||||
}
|
||||
}
|
||||
|
||||
/** Signed marks must be encoded. */
|
||||
@@ -226,6 +259,13 @@ public final class DomainFlowTmchUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/** Signed marks list cannot be empty. */
|
||||
static class SignedMarksListEmptyException extends RequiredParameterMissingException {
|
||||
public SignedMarksListEmptyException() {
|
||||
super("Signed marks list cannot be empty");
|
||||
}
|
||||
}
|
||||
|
||||
/** Only one signed mark is allowed per application. */
|
||||
static class TooManySignedMarksException extends ParameterValuePolicyErrorException {
|
||||
public TooManySignedMarksException() {
|
||||
@@ -245,6 +285,11 @@ public final class DomainFlowTmchUtils {
|
||||
public SignedMarkParsingErrorException() {
|
||||
super("Error while parsing encoded signed mark data");
|
||||
}
|
||||
|
||||
public SignedMarkParsingErrorException(Throwable cause) {
|
||||
this();
|
||||
initCause(cause);
|
||||
}
|
||||
}
|
||||
|
||||
/** Signed mark data is improperly encoded. */
|
||||
@@ -252,6 +297,11 @@ public final class DomainFlowTmchUtils {
|
||||
public SignedMarkEncodingErrorException() {
|
||||
super("Signed mark data is improperly encoded");
|
||||
}
|
||||
|
||||
public SignedMarkEncodingErrorException(Throwable cause) {
|
||||
this();
|
||||
initCause(cause);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package google.registry.flows.domain;
|
||||
|
||||
import static com.google.common.base.Ascii.toLowerCase;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.base.Strings.emptyToNull;
|
||||
@@ -23,6 +24,7 @@ import static com.google.common.collect.Sets.difference;
|
||||
import static com.google.common.collect.Sets.intersection;
|
||||
import static com.google.common.collect.Sets.union;
|
||||
import static google.registry.bsa.persistence.BsaLabelUtils.isLabelBlocked;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureName.FORBID_INSECURE_ALGORITHMS_RFC_9904;
|
||||
import static google.registry.model.domain.Domain.MAX_REGISTRATION_YEARS;
|
||||
import static google.registry.model.domain.token.AllocationToken.TokenType.REGISTER_BSA;
|
||||
import static google.registry.model.tld.Tld.TldState.GENERAL_AVAILABILITY;
|
||||
@@ -43,8 +45,8 @@ import static google.registry.util.CollectionUtils.nullToEmpty;
|
||||
import static google.registry.util.DateTimeUtils.END_INSTANT;
|
||||
import static google.registry.util.DateTimeUtils.isAtOrAfter;
|
||||
import static google.registry.util.DateTimeUtils.minusDays;
|
||||
import static google.registry.util.DateTimeUtils.plusYears;
|
||||
import static google.registry.util.DomainNameUtils.ACE_PREFIX;
|
||||
import static java.time.ZoneOffset.UTC;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
import com.google.common.base.CharMatcher;
|
||||
@@ -73,6 +75,7 @@ import google.registry.model.EppResource;
|
||||
import google.registry.model.billing.BillingBase.Flag;
|
||||
import google.registry.model.billing.BillingBase.Reason;
|
||||
import google.registry.model.billing.BillingRecurrence;
|
||||
import google.registry.model.common.FeatureFlag;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainCommand.Create;
|
||||
import google.registry.model.domain.DomainCommand.CreateOrUpdate;
|
||||
@@ -154,7 +157,7 @@ public class DomainFlowUtils {
|
||||
/** Warning message for allocation of collision domains in sunrise. */
|
||||
public static final String COLLISION_MESSAGE =
|
||||
"Domain on the name collision list was allocated. But by policy, the domain will not be "
|
||||
+ "delegated. Please visit https://www.icann.org/namecollision for more information on "
|
||||
+ "delegated. Please visit https://www.icann.org/namecollision for more information on "
|
||||
+ "name collision.";
|
||||
|
||||
/** Strict validator for ascii lowercase letters, digits, and "-", allowing "." as a separator */
|
||||
@@ -173,6 +176,27 @@ public class DomainFlowUtils {
|
||||
/** Maximum number of characters in a domain label, from RFC 2181. */
|
||||
private static final int MAX_LABEL_SIZE = 63;
|
||||
|
||||
/// Given a tld name or domain label, returns a label that can be safely logged, or
|
||||
/// `Optional.empty()` if `label` is null or blank.
|
||||
///
|
||||
/// If `label` contains disallowed characters, they are replaced with '-'. If the resulting
|
||||
/// string has a valid length, it is returned as is; if not, the first {@link #MAX_LABEL_SIZE}
|
||||
/// chars plus a suffix of `...` is returned.
|
||||
///
|
||||
/// This method is mainly for use by `FlowReporter#recordToLogs()` to ensure that the logs
|
||||
/// can be safely and correctly queried by SQL queries. See b/534931586 for more information.
|
||||
public static Optional<String> toLogSafeLabel(String label) {
|
||||
if (label == null || label.isBlank()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
var newLabel = ALLOWED_CHARS.negate().replaceFrom(toLowerCase(label), '-');
|
||||
if (newLabel.length() <= MAX_LABEL_SIZE) {
|
||||
return Optional.of(newLabel);
|
||||
} else {
|
||||
return Optional.of(newLabel.substring(0, MAX_LABEL_SIZE) + "...");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns parsed version of {@code name} if domain name label follows our naming rules and is
|
||||
* under one of the given allowed TLDs.
|
||||
@@ -341,7 +365,7 @@ public class DomainFlowUtils {
|
||||
}
|
||||
ImmutableList<DomainDsData> invalidAlgorithms =
|
||||
dsData.stream()
|
||||
.filter(ds -> !validateAlgorithm(ds.getAlgorithm()))
|
||||
.filter(ds -> algorithmIsInvalid(ds.getAlgorithm()))
|
||||
.collect(toImmutableList());
|
||||
if (!invalidAlgorithms.isEmpty()) {
|
||||
throw new InvalidDsRecordException(
|
||||
@@ -349,9 +373,16 @@ public class DomainFlowUtils {
|
||||
"Domain contains DS record(s) with an invalid algorithm wire value: %s",
|
||||
invalidAlgorithms));
|
||||
}
|
||||
boolean forbidInsecureTypes = FeatureFlag.isActiveNow(FORBID_INSECURE_ALGORITHMS_RFC_9904);
|
||||
ImmutableList<DomainDsData> invalidDigestTypes =
|
||||
dsData.stream()
|
||||
.filter(ds -> DigestType.fromWireValue(ds.getDigestType()).isEmpty())
|
||||
.filter(
|
||||
ds -> {
|
||||
Optional<DigestType> digestType = DigestType.fromWireValue(ds.getDigestType());
|
||||
return digestType
|
||||
.map(type -> forbidInsecureTypes && !type.isAllowedInRfc9904())
|
||||
.orElse(true);
|
||||
})
|
||||
.collect(toImmutableList());
|
||||
if (!invalidDigestTypes.isEmpty()) {
|
||||
throw new InvalidDsRecordException(
|
||||
@@ -376,14 +407,18 @@ public class DomainFlowUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean validateAlgorithm(int alg) {
|
||||
public static boolean algorithmIsInvalid(int alg) {
|
||||
if (alg > 255 || alg < 0) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
if (DomainDsData.FORBIDDEN_ALGORITHMS.contains(alg)
|
||||
&& FeatureFlag.isActiveNow(FORBID_INSECURE_ALGORITHMS_RFC_9904)) {
|
||||
return true;
|
||||
}
|
||||
// Algorithms that are reserved or unassigned will just return a string representation of their
|
||||
// integer wire value.
|
||||
String algorithm = Algorithm.string(alg);
|
||||
return !algorithm.equals(Integer.toString(alg));
|
||||
return algorithm.equals(Integer.toString(alg));
|
||||
}
|
||||
|
||||
/** We only allow specifying years in a period. */
|
||||
@@ -581,13 +616,12 @@ public class DomainFlowUtils {
|
||||
InternetDomainName domainName,
|
||||
Optional<Domain> domain,
|
||||
@Nullable CurrencyUnit topLevelCurrency,
|
||||
Instant currentDate,
|
||||
Instant now,
|
||||
DomainPricingLogic pricingLogic,
|
||||
Optional<AllocationToken> allocationToken,
|
||||
boolean isAvailable,
|
||||
@Nullable BillingRecurrence billingRecurrence)
|
||||
throws EppException {
|
||||
Instant now = currentDate;
|
||||
// Use the custom effective date specified in the fee check request, if there is one.
|
||||
if (feeRequest.getEffectiveDate().isPresent()) {
|
||||
now = feeRequest.getEffectiveDate().get();
|
||||
@@ -630,6 +664,7 @@ public class DomainFlowUtils {
|
||||
tld,
|
||||
domainNameString,
|
||||
now,
|
||||
domain,
|
||||
years,
|
||||
isAnchorTenant(domainName, allocationToken, Optional.empty()),
|
||||
isSunrise,
|
||||
@@ -753,6 +788,9 @@ public class DomainFlowUtils {
|
||||
if (!feesAndCredits.getEapCost().isZero()) {
|
||||
throw new FeesRequiredDuringEarlyAccessProgramException(feesAndCredits.getEapCost());
|
||||
}
|
||||
if (!feesAndCredits.getXapCost().isZero()) {
|
||||
throw new FeesRequiredDuringExpiryAccessPeriodException(feesAndCredits.getXapCost());
|
||||
}
|
||||
if (feesAndCredits.getTotalCost().isZero() || !feesAndCredits.isFeeExtensionRequired()) {
|
||||
return;
|
||||
}
|
||||
@@ -816,7 +854,7 @@ public class DomainFlowUtils {
|
||||
return fee.getType();
|
||||
}
|
||||
ImmutableList<FeeType> types = fee.parseDescriptionForTypes();
|
||||
if (types.size() == 0) {
|
||||
if (types.isEmpty()) {
|
||||
throw new FeeDescriptionParseException(fee.getDescription());
|
||||
} else if (types.size() > 1) {
|
||||
throw new FeeDescriptionMultipleMatchesException(fee.getDescription(), types);
|
||||
@@ -848,7 +886,7 @@ public class DomainFlowUtils {
|
||||
*/
|
||||
public static void validateRegistrationPeriod(Instant now, Instant newExpirationTime)
|
||||
throws EppException {
|
||||
if (now.atZone(UTC).plusYears(MAX_REGISTRATION_YEARS).toInstant().isBefore(newExpirationTime)) {
|
||||
if (plusYears(now, MAX_REGISTRATION_YEARS).isBefore(newExpirationTime)) {
|
||||
throw new ExceedsMaxRegistrationYearsException();
|
||||
}
|
||||
}
|
||||
@@ -907,7 +945,7 @@ public class DomainFlowUtils {
|
||||
return ImmutableSet.copyOf(union(difference(oldDsData, toRemove), toAdd));
|
||||
}
|
||||
|
||||
/** If a domain "clientUpdateProhibited" set, updates must clear it or fail. */
|
||||
/** If a domain has "clientUpdateProhibited" set, updates must clear it or fail. */
|
||||
static void verifyClientUpdateNotProhibited(Update command, Domain existingResource)
|
||||
throws ResourceHasClientUpdateProhibitedException {
|
||||
if (existingResource.getStatusValues().contains(StatusValue.CLIENT_UPDATE_PROHIBITED)
|
||||
@@ -996,7 +1034,13 @@ public class DomainFlowUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/** Check that the claims period hasn't ended. */
|
||||
/**
|
||||
* Check that the claims period hasn't ended.
|
||||
*
|
||||
* @param tld the {@link Tld} to check
|
||||
* @param now the current {@link Instant}
|
||||
* @throws ClaimsPeriodEndedException if the claims period has ended
|
||||
*/
|
||||
static void verifyClaimsPeriodNotEnded(Tld tld, Instant now) throws ClaimsPeriodEndedException {
|
||||
if (!now.isBefore(tld.getClaimsPeriodEnd())) {
|
||||
throw new ClaimsPeriodEndedException(tld.getTldStr());
|
||||
@@ -1008,6 +1052,9 @@ public class DomainFlowUtils {
|
||||
*
|
||||
* <p>{@link BigDecimal} has a concept of significant figures, so zero is not always zero. E.g.
|
||||
* zero in USD is 0.00, whereas zero in Yen is 0, and zero in Dinars is 0.000 (!).
|
||||
*
|
||||
* @param currencyUnit the {@link CurrencyUnit}
|
||||
* @return zero in the given currency
|
||||
*/
|
||||
static BigDecimal zeroInCurrency(CurrencyUnit currencyUnit) {
|
||||
return Money.of(currencyUnit, BigDecimal.ZERO).getAmount();
|
||||
@@ -1016,6 +1063,12 @@ public class DomainFlowUtils {
|
||||
/**
|
||||
* Check that if there's a claims notice it's on the claims list, and that if there's not one it's
|
||||
* not on the claims list.
|
||||
*
|
||||
* @param domainName the {@link InternetDomainName} to check
|
||||
* @param claimsList the current {@link ClaimsList}
|
||||
* @param hasSignedMarks whether signed marks are present
|
||||
* @param hasClaimsNotice whether a claims notice is present
|
||||
* @throws EppException if the claims notice status is incorrect
|
||||
*/
|
||||
static void verifyClaimsNoticeIfAndOnlyIfNeeded(
|
||||
InternetDomainName domainName,
|
||||
@@ -1032,7 +1085,12 @@ public class DomainFlowUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/** Check that there are no code marks, which is a type of mark we don't support. */
|
||||
/**
|
||||
* Check that there are no code marks, which is a type of mark we don't support.
|
||||
*
|
||||
* @param launchCreate the {@link LaunchCreateExtension}
|
||||
* @throws UnsupportedMarkTypeException if code marks are present
|
||||
*/
|
||||
static void verifyNoCodeMarks(LaunchCreateExtension launchCreate)
|
||||
throws UnsupportedMarkTypeException {
|
||||
if (launchCreate.hasCodeMarks()) {
|
||||
@@ -1040,7 +1098,13 @@ public class DomainFlowUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/** Create a response extension listing the fees on a domain create. */
|
||||
/**
|
||||
* Create a response extension listing the fees on a domain create.
|
||||
*
|
||||
* @param feeCreate the {@link FeeTransformCommandExtension}
|
||||
* @param feesAndCredits the {@link FeesAndCredits}
|
||||
* @return the {@link FeeTransformResponseExtension}
|
||||
*/
|
||||
static FeeTransformResponseExtension createFeeCreateResponse(
|
||||
FeeTransformCommandExtension feeCreate, FeesAndCredits feesAndCredits) {
|
||||
return feeCreate
|
||||
@@ -1058,10 +1122,21 @@ public class DomainFlowUtils {
|
||||
* their flow. For example, if a grace period delete occurs, we must add -1 counters for the
|
||||
* associated NET_ADDS_#_YRS field, if it exists.
|
||||
*
|
||||
* <p>The steps are as follows: 1. Find all HistoryEntries under the domain modified in the past,
|
||||
* up to the maxSearchPeriod. 2. Only keep HistoryEntries with a DomainTransactionRecord that a)
|
||||
* hasn't been reported yet and b) matches the predicate 3. Return the transactionRecords under
|
||||
* the most recent HistoryEntry that fits the above criteria, with negated reportAmounts.
|
||||
* <p>The steps are as follows:
|
||||
*
|
||||
* <ol>
|
||||
* <li>Find all HistoryEntries under the domain modified in the past, up to the maxSearchPeriod.
|
||||
* <li>Only keep HistoryEntries with a DomainTransactionRecord that a) hasn't been reported yet
|
||||
* and b) matches the predicate
|
||||
* <li>Return the transactionRecords under the most recent HistoryEntry that fits the above
|
||||
* criteria, with negated reportAmounts.
|
||||
* </ol>
|
||||
*
|
||||
* @param domain the {@link Domain} to create records for
|
||||
* @param now the current {@link Instant}
|
||||
* @param maxSearchPeriod the {@link Duration} to search back
|
||||
* @param cancelableFields the set of {@link TransactionReportField}s that can be canceled
|
||||
* @return the set of canceling {@link DomainTransactionRecord}s
|
||||
*/
|
||||
public static ImmutableSet<DomainTransactionRecord> createCancelingRecords(
|
||||
Domain domain,
|
||||
@@ -1119,6 +1194,32 @@ public class DomainFlowUtils {
|
||||
.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the domain was deleted during its Add Grace Period (AGP).
|
||||
*
|
||||
* <p>Per policy, domains deleted during AGP are not subject to Expiry Access Period (XAP) fees or
|
||||
* reservation restrictions upon subsequent re-registration.
|
||||
*/
|
||||
public static boolean wasDeletedDuringAddGracePeriod(Domain domain, Tld tld) {
|
||||
if (domain.getCreationTime() == null || domain.getDeletionTime() == null) {
|
||||
return false;
|
||||
}
|
||||
return !domain
|
||||
.getDeletionTime()
|
||||
.isAfter(domain.getCreationTime().plus(tld.getAddGracePeriodLength()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the domain was deleted before {@code now} and is eligible for Expiry Access
|
||||
* Period (XAP) evaluation.
|
||||
*/
|
||||
public static boolean isDomainEligibleForXap(Domain domain, Tld tld, Instant now) {
|
||||
if (domain.getDeletionTime() == null || !domain.getDeletionTime().isBefore(now)) {
|
||||
return false;
|
||||
}
|
||||
return !wasDeletedDuringAddGracePeriod(domain, tld);
|
||||
}
|
||||
|
||||
/** Resource linked to this domain does not exist. */
|
||||
static class LinkedResourcesDoNotExistException extends ObjectDoesNotExistException {
|
||||
public LinkedResourcesDoNotExistException(Class<?> type, ImmutableSet<String> resourceIds) {
|
||||
@@ -1225,13 +1326,6 @@ public class DomainFlowUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/** Having a registrant is prohibited by registry policy. */
|
||||
public static class RegistrantProhibitedException extends ParameterValuePolicyErrorException {
|
||||
public RegistrantProhibitedException() {
|
||||
super("Having a registrant is prohibited by registry policy");
|
||||
}
|
||||
}
|
||||
|
||||
/** Too many nameservers set on this domain. */
|
||||
static class TooManyNameserversException extends ParameterValuePolicyErrorException {
|
||||
public TooManyNameserversException(String message) {
|
||||
@@ -1313,6 +1407,18 @@ public class DomainFlowUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/** Fees must be explicitly acknowledged when creating domains during the Expiry Access Period. */
|
||||
static class FeesRequiredDuringExpiryAccessPeriodException
|
||||
extends RequiredParameterMissingException {
|
||||
|
||||
public FeesRequiredDuringExpiryAccessPeriodException(Money expectedFee) {
|
||||
super(
|
||||
"Fees must be explicitly acknowledged when creating domains "
|
||||
+ "during the Expiry Access Period. The XAP fee is: "
|
||||
+ expectedFee);
|
||||
}
|
||||
}
|
||||
|
||||
/** The 'grace-period', 'applied' and 'refundable' fields are disallowed by server policy. */
|
||||
static class UnsupportedFeeAttributeException extends UnimplementedOptionException {
|
||||
UnsupportedFeeAttributeException() {
|
||||
@@ -1384,6 +1490,13 @@ public class DomainFlowUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/** Having a registrant is prohibited by registry policy. */
|
||||
public static class RegistrantProhibitedException extends ParameterValuePolicyErrorException {
|
||||
public RegistrantProhibitedException() {
|
||||
super("Having a registrant is prohibited by registry policy");
|
||||
}
|
||||
}
|
||||
|
||||
/** The fee description passed in the transform command cannot be parsed. */
|
||||
public static class FeeDescriptionParseException extends ParameterValuePolicyErrorException {
|
||||
public FeeDescriptionParseException(String description) {
|
||||
|
||||
@@ -15,13 +15,18 @@
|
||||
package google.registry.flows.domain;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.isDomainEligibleForXap;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.zeroInCurrency;
|
||||
import static google.registry.flows.domain.token.AllocationTokenFlowUtils.discountTokenInvalidForPremiumName;
|
||||
import static google.registry.pricing.PricingEngineProxy.getPricesForDomainName;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Range;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import google.registry.config.RegistryConfig;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.custom.DomainPricingCustomLogic;
|
||||
import google.registry.flows.custom.DomainPricingCustomLogic.CreatePriceParameters;
|
||||
@@ -31,6 +36,7 @@ import google.registry.flows.custom.DomainPricingCustomLogic.TransferPriceParame
|
||||
import google.registry.flows.custom.DomainPricingCustomLogic.UpdatePriceParameters;
|
||||
import google.registry.model.billing.BillingBase.RenewalPriceBehavior;
|
||||
import google.registry.model.billing.BillingRecurrence;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.fee.BaseFee;
|
||||
import google.registry.model.domain.fee.BaseFee.FeeType;
|
||||
import google.registry.model.domain.fee.Fee;
|
||||
@@ -40,7 +46,9 @@ import google.registry.model.domain.token.AllocationToken.TokenBehavior;
|
||||
import google.registry.model.pricing.PremiumPricingEngine.DomainPrices;
|
||||
import google.registry.model.tld.Tld;
|
||||
import jakarta.inject.Inject;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -54,11 +62,28 @@ import org.joda.money.Money;
|
||||
*/
|
||||
public final class DomainPricingLogic {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private final DomainPricingCustomLogic customLogic;
|
||||
private final Duration expiryAccessPeriodTotalLength;
|
||||
private final Duration expiryAccessPeriodTierLength;
|
||||
private final ImmutableMap<CurrencyUnit, BigDecimal> expiryAccessPeriodInitialFee;
|
||||
private final ImmutableMap<CurrencyUnit, BigDecimal> expiryAccessPeriodFinalFee;
|
||||
|
||||
@Inject
|
||||
public DomainPricingLogic(DomainPricingCustomLogic customLogic) {
|
||||
public DomainPricingLogic(
|
||||
DomainPricingCustomLogic customLogic,
|
||||
@Config("domainExpiryAccessPeriodTotalLength") Duration expiryAccessPeriodTotalLength,
|
||||
@Config("domainExpiryAccessPeriodTierLength") Duration expiryAccessPeriodTierLength,
|
||||
@Config("domainExpiryAccessPeriodInitialFee")
|
||||
ImmutableMap<CurrencyUnit, BigDecimal> expiryAccessPeriodInitialFee,
|
||||
@Config("domainExpiryAccessPeriodFinalFee")
|
||||
ImmutableMap<CurrencyUnit, BigDecimal> expiryAccessPeriodFinalFee) {
|
||||
this.customLogic = customLogic;
|
||||
this.expiryAccessPeriodTotalLength = expiryAccessPeriodTotalLength;
|
||||
this.expiryAccessPeriodTierLength = expiryAccessPeriodTierLength;
|
||||
this.expiryAccessPeriodInitialFee = expiryAccessPeriodInitialFee;
|
||||
this.expiryAccessPeriodFinalFee = expiryAccessPeriodFinalFee;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,35 +96,23 @@ public final class DomainPricingLogic {
|
||||
Tld tld,
|
||||
String domainName,
|
||||
Instant dateTime,
|
||||
Optional<Domain> recentlyDeletedDomain,
|
||||
int years,
|
||||
boolean isAnchorTenant,
|
||||
boolean isSunriseCreate,
|
||||
Optional<AllocationToken> allocationToken)
|
||||
throws EppException {
|
||||
CurrencyUnit currency = tld.getCurrency();
|
||||
|
||||
BaseFee createFee;
|
||||
// Domain create cost is always zero for anchor tenants
|
||||
if (isAnchorTenant) {
|
||||
createFee = Fee.create(zeroInCurrency(currency), FeeType.CREATE, false);
|
||||
} else {
|
||||
DomainPrices domainPrices = getPricesForDomainName(domainName, dateTime);
|
||||
if (allocationToken.isPresent()) {
|
||||
// Handle any special NONPREMIUM / SPECIFIED cases configured in the token
|
||||
domainPrices =
|
||||
applyTokenToDomainPrices(domainPrices, tld, dateTime, years, allocationToken.get());
|
||||
}
|
||||
Money domainCreateCost =
|
||||
getDomainCreateCostWithDiscount(domainPrices, years, allocationToken, tld);
|
||||
// Apply a sunrise discount if configured and applicable
|
||||
if (isSunriseCreate) {
|
||||
domainCreateCost =
|
||||
domainCreateCost.multipliedBy(
|
||||
1.0d - RegistryConfig.getSunriseDomainCreateDiscount(), RoundingMode.HALF_EVEN);
|
||||
}
|
||||
createFee =
|
||||
Fee.create(domainCreateCost.getAmount(), FeeType.CREATE, domainPrices.isPremium());
|
||||
}
|
||||
Fee createFee =
|
||||
computeCreateFee(
|
||||
tld,
|
||||
domainName,
|
||||
dateTime,
|
||||
years,
|
||||
isAnchorTenant,
|
||||
isSunriseCreate,
|
||||
allocationToken,
|
||||
currency);
|
||||
|
||||
// Create fees for the cost and the EAP fee, if any.
|
||||
Fee eapFee = tld.getEapFeeFor(dateTime);
|
||||
@@ -109,6 +122,7 @@ public final class DomainPricingLogic {
|
||||
if (!isAnchorTenant && !eapFee.hasZeroCost()) {
|
||||
feesBuilder.addFeeOrCredit(eapFee);
|
||||
}
|
||||
maybeAddXapFee(tld, dateTime, recentlyDeletedDomain, currency, feesBuilder);
|
||||
|
||||
// Apply custom logic to the create fee, if any.
|
||||
return customLogic.customizeCreatePrice(
|
||||
@@ -121,6 +135,113 @@ public final class DomainPricingLogic {
|
||||
.build());
|
||||
}
|
||||
|
||||
private Fee computeCreateFee(
|
||||
Tld tld,
|
||||
String domainName,
|
||||
Instant dateTime,
|
||||
int years,
|
||||
boolean isAnchorTenant,
|
||||
boolean isSunriseCreate,
|
||||
Optional<AllocationToken> allocationToken,
|
||||
CurrencyUnit currency)
|
||||
throws EppException {
|
||||
// Domain create cost is always zero for anchor tenants
|
||||
if (isAnchorTenant) {
|
||||
return Fee.create(zeroInCurrency(currency), FeeType.CREATE, false);
|
||||
}
|
||||
DomainPrices domainPrices = getPricesForDomainName(domainName, dateTime);
|
||||
if (allocationToken.isPresent()) {
|
||||
// Handle any special NONPREMIUM / SPECIFIED cases configured in the token
|
||||
domainPrices =
|
||||
applyTokenToDomainPrices(domainPrices, tld, dateTime, years, allocationToken.get());
|
||||
}
|
||||
Money domainCreateCost =
|
||||
getDomainCreateCostWithDiscount(domainPrices, years, allocationToken, tld);
|
||||
// Apply a sunrise discount if configured and applicable
|
||||
if (isSunriseCreate) {
|
||||
domainCreateCost =
|
||||
domainCreateCost.multipliedBy(
|
||||
1.0d - RegistryConfig.getSunriseDomainCreateDiscount(), RoundingMode.HALF_EVEN);
|
||||
}
|
||||
return Fee.create(domainCreateCost.getAmount(), FeeType.CREATE, domainPrices.isPremium());
|
||||
}
|
||||
|
||||
private void maybeAddXapFee(
|
||||
Tld tld,
|
||||
Instant dateTime,
|
||||
Optional<Domain> recentlyDeletedDomain,
|
||||
CurrencyUnit currency,
|
||||
FeesAndCredits.Builder feesBuilder) {
|
||||
if (tld.getExpiryAccessPeriodModeAt(dateTime) == Tld.ExpiryAccessPeriodMode.ENABLED
|
||||
&& recentlyDeletedDomain.isPresent()
|
||||
&& isDomainEligibleForXap(recentlyDeletedDomain.get(), tld, dateTime)) {
|
||||
Optional<Fee> xapFee =
|
||||
getXapFeeFor(dateTime, recentlyDeletedDomain.get().getDeletionTime(), currency);
|
||||
xapFee.ifPresent(
|
||||
fee -> {
|
||||
feesBuilder.addFeeOrCredit(fee);
|
||||
if (!fee.hasZeroCost()) {
|
||||
feesBuilder.setFeeExtensionRequired(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates and returns the Expiry Access Fee for a recently deleted domain at the given time.
|
||||
*/
|
||||
Optional<Fee> getXapFeeFor(Instant dateTime, Instant deletionTime, CurrencyUnit currency) {
|
||||
Duration elapsedTimeInXap = Duration.between(deletionTime, dateTime);
|
||||
if (!expiryAccessPeriodInitialFee.containsKey(currency)
|
||||
|| !expiryAccessPeriodFinalFee.containsKey(currency)) {
|
||||
// If the XAP schedule hasn't been configured in YAML for the currency this TLD uses, log the
|
||||
// error and then short-circuit return (to allow the EPP flow to continue normally).
|
||||
logger.atSevere().log(
|
||||
"Expiry Access Period configuration is lacking initial or final fees for currency %s.",
|
||||
currency);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// Determine which tier the current time falls into (0-indexed).
|
||||
long tier = elapsedTimeInXap.toMillis() / expiryAccessPeriodTierLength.toMillis();
|
||||
long numTiers =
|
||||
expiryAccessPeriodTotalLength.toMillis() / expiryAccessPeriodTierLength.toMillis();
|
||||
if (tier < 0 || tier >= numTiers) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// Calculate the parameters for the geometric sequence: XAP fee = initialFee * ratio ^ tier.
|
||||
BigDecimal xapFee;
|
||||
if (expiryAccessPeriodInitialFee.get(currency).signum() == 0 || numTiers <= 1 || tier == 0) {
|
||||
xapFee =
|
||||
expiryAccessPeriodInitialFee
|
||||
.get(currency)
|
||||
.setScale(currency.getDecimalPlaces(), RoundingMode.HALF_EVEN);
|
||||
} else {
|
||||
double base =
|
||||
expiryAccessPeriodFinalFee.get(currency).doubleValue()
|
||||
/ expiryAccessPeriodInitialFee.get(currency).doubleValue();
|
||||
double exponent = 1.0 / (numTiers - 1.0);
|
||||
BigDecimal ratio = BigDecimal.valueOf(Math.pow(base, exponent));
|
||||
BigDecimal fee = expiryAccessPeriodInitialFee.get(currency).multiply(ratio.pow((int) tier));
|
||||
xapFee = fee.setScale(currency.getDecimalPlaces(), RoundingMode.HALF_EVEN);
|
||||
}
|
||||
|
||||
Range<Instant> validPeriod =
|
||||
Range.closedOpen(
|
||||
deletionTime.plus(expiryAccessPeriodTierLength.multipliedBy(tier)),
|
||||
deletionTime.plus(expiryAccessPeriodTierLength.multipliedBy(tier + 1)));
|
||||
return Optional.of(
|
||||
Fee.create(
|
||||
xapFee,
|
||||
FeeType.XAP,
|
||||
// An XAP fee does not count as premium -- it's a separate one-time fee, independent of
|
||||
// which the domain is separately considered standard vs premium.
|
||||
false,
|
||||
validPeriod,
|
||||
validPeriod.upperEndpoint()));
|
||||
}
|
||||
|
||||
/** Returns a new renewal cost for the pricer. */
|
||||
public FeesAndCredits getRenewPrice(
|
||||
Tld tld,
|
||||
@@ -283,7 +404,7 @@ public final class DomainPricingLogic {
|
||||
return tld.getStandardRenewCost(dateTime).multipliedBy(years);
|
||||
}
|
||||
if (token.getRenewalPriceBehavior().equals(RenewalPriceBehavior.SPECIFIED)) {
|
||||
return token.getRenewalPrice().get();
|
||||
return token.getRenewalPrice().get().multipliedBy(years);
|
||||
}
|
||||
}
|
||||
return getDomainCostWithDiscount(
|
||||
@@ -328,12 +449,13 @@ public final class DomainPricingLogic {
|
||||
// Apply the allocation token discount, if applicable.
|
||||
if (token.getDiscountPrice().isPresent()
|
||||
&& tld.getCurrency().equals(token.getDiscountPrice().get().getCurrencyUnit())) {
|
||||
int nonDiscountedYears = Math.max(0, years - token.getDiscountYears());
|
||||
int discountedYears = Math.min(years, token.getDiscountYears());
|
||||
int nonDiscountedYears = years - discountedYears;
|
||||
totalDomainFlowCost =
|
||||
token
|
||||
.getDiscountPrice()
|
||||
.get()
|
||||
.multipliedBy(token.getDiscountYears())
|
||||
.multipliedBy(discountedYears)
|
||||
.plus(subsequentYearCost.orElse(firstYearCost).multipliedBy(nonDiscountedYears));
|
||||
} else if (token.getDiscountFraction() > 0) {
|
||||
int discountedYears = Math.min(years, token.getDiscountYears());
|
||||
|
||||
@@ -34,8 +34,8 @@ import static google.registry.flows.domain.token.AllocationTokenFlowUtils.maybeA
|
||||
import static google.registry.flows.domain.token.AllocationTokenFlowUtils.verifyBulkTokenAllowedOnDomain;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_RENEW;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.DateTimeUtils.plusYears;
|
||||
import static google.registry.util.DateTimeUtils.toLocalDate;
|
||||
import static java.time.ZoneOffset.UTC;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -192,11 +192,7 @@ public final class DomainRenewFlow implements MutatingFlow {
|
||||
existingDomain = maybeApplyBulkPricingRemovalToken(existingDomain, allocationToken);
|
||||
|
||||
Instant newExpirationTime =
|
||||
existingDomain
|
||||
.getRegistrationExpirationTime()
|
||||
.atZone(UTC)
|
||||
.plusYears(years)
|
||||
.toInstant(); // Uncapped
|
||||
plusYears(existingDomain.getRegistrationExpirationTime(), years); // Uncapped
|
||||
validateRegistrationPeriod(now, newExpirationTime);
|
||||
Optional<FeeRenewCommandExtension> feeRenew =
|
||||
eppInput.getSingleExtension(FeeRenewCommandExtension.class);
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
package google.registry.flows.domain;
|
||||
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
|
||||
import static com.google.common.collect.Sets.symmetricDifference;
|
||||
import static com.google.common.collect.Sets.union;
|
||||
@@ -58,17 +57,17 @@ import google.registry.flows.custom.DomainUpdateFlowCustomLogic.BeforeSaveParame
|
||||
import google.registry.flows.custom.EntityChanges;
|
||||
import google.registry.flows.domain.DomainFlowUtils.NameserversNotSpecifiedForTldWithNameserverAllowListException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.RegistrantProhibitedException;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.billing.BillingBase.Reason;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainCommand.Update;
|
||||
import google.registry.model.domain.DomainCommand.Update.AddRemove;
|
||||
import google.registry.model.domain.DomainCommand.Update.Change;
|
||||
import google.registry.model.domain.DomainCommand.Update.DomainAddRemove;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.fee.FeeUpdateCommandExtension;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.domain.secdns.DomainDsData;
|
||||
import google.registry.model.domain.secdns.SecDnsUpdateExtension;
|
||||
import google.registry.model.domain.secdns.SecDnsUpdateExtension.Add;
|
||||
import google.registry.model.domain.secdns.SecDnsUpdateExtension.Remove;
|
||||
@@ -118,6 +117,7 @@ import java.util.Optional;
|
||||
* @error {@link NameserversNotSpecifiedForTldWithNameserverAllowListException}
|
||||
* @error {@link DomainFlowUtils.NotAuthorizedForTldException}
|
||||
* @error {@link RegistrantProhibitedException}
|
||||
* @error {@link ContactsProhibitedException}
|
||||
* @error {@link DomainFlowUtils.SecDnsAllUsageException}
|
||||
* @error {@link DomainFlowUtils.TooManyDsRecordsException}
|
||||
* @error {@link DomainFlowUtils.TooManyNameserversException}
|
||||
@@ -214,8 +214,8 @@ public final class DomainUpdateFlow implements MutatingFlow {
|
||||
private void verifyUpdateAllowed(Update command, Domain existingDomain, Instant now)
|
||||
throws EppException {
|
||||
verifyOptionalAuthInfo(authInfo, existingDomain);
|
||||
AddRemove add = command.getInnerAdd();
|
||||
AddRemove remove = command.getInnerRemove();
|
||||
DomainAddRemove add = command.getInnerAdd();
|
||||
DomainAddRemove remove = command.getInnerRemove();
|
||||
String tldStr = existingDomain.getTld();
|
||||
if (!isSuperuser) {
|
||||
verifyNoDisallowedStatuses(existingDomain, UPDATE_DISALLOWED_STATUSES);
|
||||
@@ -234,8 +234,8 @@ public final class DomainUpdateFlow implements MutatingFlow {
|
||||
}
|
||||
|
||||
private Domain performUpdate(Update command, Domain domain, Instant now) throws EppException {
|
||||
AddRemove add = command.getInnerAdd();
|
||||
AddRemove remove = command.getInnerRemove();
|
||||
DomainAddRemove add = command.getInnerAdd();
|
||||
DomainAddRemove remove = command.getInnerRemove();
|
||||
Optional<SecDnsUpdateExtension> secDnsUpdate =
|
||||
eppInput.getSingleExtension(SecDnsUpdateExtension.class);
|
||||
verifyAddsAndRemoves(domain.getNameservers(), add.getNameservers(), remove.getNameservers());
|
||||
@@ -251,28 +251,29 @@ public final class DomainUpdateFlow implements MutatingFlow {
|
||||
Domain.Builder domainBuilder =
|
||||
domain
|
||||
.asBuilder()
|
||||
// Handle the secDNS extension. As dsData in secDnsUpdate is read from EPP input and
|
||||
// does not have domainRepoId set, we create a copy of the existing dsData without
|
||||
// domainRepoId for comparison.
|
||||
// Handle the secDNS extension.
|
||||
.setDsData(
|
||||
secDnsUpdate.isPresent()
|
||||
? updateDsData(
|
||||
domain.getDsData().stream()
|
||||
.map(DomainDsData::cloneWithoutDomainRepoId)
|
||||
.collect(toImmutableSet()),
|
||||
secDnsUpdate.get())
|
||||
? updateDsData(domain.getDsData(), secDnsUpdate.get())
|
||||
: domain.getDsData())
|
||||
.setLastEppUpdateTime(now)
|
||||
.setLastEppUpdateRegistrarId(registrarId)
|
||||
.addStatusValues(add.getStatusValues())
|
||||
.removeStatusValues(remove.getStatusValues())
|
||||
.setAuthInfo(Optional.ofNullable(change.getAuthInfo()).orElse(domain.getAuthInfo()));
|
||||
.setLastEppUpdateRegistrarId(registrarId);
|
||||
|
||||
if (!add.getStatusValues().isEmpty()) {
|
||||
domainBuilder.addStatusValues(add.getStatusValues());
|
||||
}
|
||||
if (!remove.getStatusValues().isEmpty()) {
|
||||
domainBuilder.removeStatusValues(remove.getStatusValues());
|
||||
}
|
||||
|
||||
domainBuilder.setAuthInfo(
|
||||
Optional.ofNullable(change.getAuthInfo()).orElse(domain.getAuthInfo()));
|
||||
|
||||
if (!add.getNameservers().isEmpty()) {
|
||||
domainBuilder.addNameservers(add.getNameservers().stream().collect(toImmutableSet()));
|
||||
domainBuilder.addNameservers(add.getNameservers());
|
||||
}
|
||||
if (!remove.getNameservers().isEmpty()) {
|
||||
domainBuilder.removeNameservers(remove.getNameservers().stream().collect(toImmutableSet()));
|
||||
domainBuilder.removeNameservers(remove.getNameservers());
|
||||
}
|
||||
|
||||
Optional<DomainUpdateSuperuserExtension> superuserExt =
|
||||
|
||||
@@ -77,6 +77,11 @@ public class FeesAndCredits extends ImmutableObject implements Buildable {
|
||||
return getTotalCostForType(FeeType.EAP);
|
||||
}
|
||||
|
||||
/** Returns the XAP cost for the event. */
|
||||
public Money getXapCost() {
|
||||
return getTotalCostForType(FeeType.XAP);
|
||||
}
|
||||
|
||||
/** Returns the renew cost for the event. */
|
||||
public Money getRenewCost() {
|
||||
return getTotalCostForType(FeeType.RENEW);
|
||||
|
||||
+15
-5
@@ -242,7 +242,14 @@ public class AllocationTokenFlowUtils {
|
||||
case CREATE ->
|
||||
pricingLogic
|
||||
.getCreatePrice(
|
||||
tld, domainName, now, yearsForAction, false, false, Optional.of(token))
|
||||
tld,
|
||||
domainName,
|
||||
now,
|
||||
Optional.empty(),
|
||||
yearsForAction,
|
||||
false,
|
||||
false,
|
||||
Optional.of(token))
|
||||
.getTotalCost()
|
||||
.getAmount();
|
||||
case RENEW ->
|
||||
@@ -270,11 +277,14 @@ public class AllocationTokenFlowUtils {
|
||||
return maybeTokenEntity.get();
|
||||
}
|
||||
|
||||
maybeTokenEntity = AllocationToken.get(VKey.create(AllocationToken.class, token));
|
||||
if (maybeTokenEntity.isEmpty()) {
|
||||
throw new NonexistentAllocationTokenException();
|
||||
VKey<AllocationToken> tokenKey = VKey.create(AllocationToken.class, token);
|
||||
AllocationToken tokenEntity =
|
||||
AllocationToken.get(tokenKey).orElseThrow(NonexistentAllocationTokenException::new);
|
||||
if (tokenEntity.getTokenType().equals(AllocationToken.TokenType.SINGLE_USE)) {
|
||||
// Reload the token to avoid possible cache race conditions where the token may have already
|
||||
// been redeemed
|
||||
tokenEntity = tm().loadByKey(tokenKey);
|
||||
}
|
||||
AllocationToken tokenEntity = maybeTokenEntity.get();
|
||||
validateTokenEntity(tokenEntity, registrarId, domainName, now);
|
||||
return tokenEntity;
|
||||
}
|
||||
|
||||
@@ -116,15 +116,21 @@ public class HostFlowUtils {
|
||||
if (inetAddresses == null) {
|
||||
return;
|
||||
}
|
||||
if (inetAddresses.stream().anyMatch(InetAddress::isLoopbackAddress)) {
|
||||
throw new LoopbackIpNotValidForHostException();
|
||||
for (InetAddress inetAddress : inetAddresses) {
|
||||
if (inetAddress.isLoopbackAddress()
|
||||
|| inetAddress.isLinkLocalAddress()
|
||||
|| inetAddress.isSiteLocalAddress()
|
||||
|| inetAddress.isAnyLocalAddress()
|
||||
|| inetAddress.isMulticastAddress()) {
|
||||
throw new IpAddressNotRoutableException(inetAddress.getHostAddress());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Loopback IPs are not valid for hosts. */
|
||||
static class LoopbackIpNotValidForHostException extends ParameterValuePolicyErrorException {
|
||||
public LoopbackIpNotValidForHostException() {
|
||||
super("Loopback IPs are not valid for hosts");
|
||||
/** IP address is not a public, routable address. */
|
||||
static class IpAddressNotRoutableException extends ParameterValuePolicyErrorException {
|
||||
public IpAddressNotRoutableException(String ipAddress) {
|
||||
super(String.format("IP address %s is not a public, globally routable address", ipAddress));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ import static google.registry.util.CollectionUtils.isNullOrEmpty;
|
||||
import com.google.cloud.tasks.v2.Task;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.batch.AsyncTaskEnqueuer;
|
||||
import google.registry.batch.CloudTasksUtils;
|
||||
import google.registry.dns.RefreshDnsOnHostRenameAction;
|
||||
import google.registry.flows.EppException;
|
||||
@@ -59,8 +58,8 @@ import google.registry.model.eppinput.ResourceCommand;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.host.HostCommand.Update;
|
||||
import google.registry.model.host.HostCommand.Update.AddRemove;
|
||||
import google.registry.model.host.HostCommand.Update.Change;
|
||||
import google.registry.model.host.HostCommand.Update.HostAddRemove;
|
||||
import google.registry.model.host.HostHistory;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import google.registry.persistence.VKey;
|
||||
@@ -122,7 +121,6 @@ public final class HostUpdateFlow implements MutatingFlow {
|
||||
@Inject @TargetId String targetId;
|
||||
@Inject @Superuser boolean isSuperuser;
|
||||
@Inject HostHistory.Builder historyBuilder;
|
||||
@Inject AsyncTaskEnqueuer asyncTaskEnqueuer;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
@Inject CloudTasksUtils cloudTasksUtils;
|
||||
|
||||
@@ -148,6 +146,7 @@ public final class HostUpdateFlow implements MutatingFlow {
|
||||
? tm().loadByKey(existingHost.getSuperordinateDomain()).cloneProjectedAtTime(now)
|
||||
: null;
|
||||
// Note that lookupSuperordinateDomain calls cloneProjectedAtTime on the domain for us.
|
||||
|
||||
Optional<Domain> newSuperordinateDomain =
|
||||
lookupSuperordinateDomain(validateHostName(newHostName), now);
|
||||
verifySuperordinateDomainNotInPendingDelete(newSuperordinateDomain.orElse(null));
|
||||
@@ -157,8 +156,8 @@ public final class HostUpdateFlow implements MutatingFlow {
|
||||
if (isHostRename && ForeignKeyUtils.loadKey(Host.class, newHostName, now).isPresent()) {
|
||||
throw new HostAlreadyExistsException(newHostName);
|
||||
}
|
||||
AddRemove add = command.getInnerAdd();
|
||||
AddRemove remove = command.getInnerRemove();
|
||||
HostAddRemove add = command.getInnerAdd();
|
||||
HostAddRemove remove = command.getInnerRemove();
|
||||
verifyAddsAndRemoves(
|
||||
existingHost.getStatusValues(), add.getStatusValues(), remove.getStatusValues());
|
||||
verifyAddsAndRemoves(
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package google.registry.flows.picker;
|
||||
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableTable;
|
||||
@@ -22,16 +23,6 @@ import google.registry.flows.EppException;
|
||||
import google.registry.flows.EppException.SyntaxErrorException;
|
||||
import google.registry.flows.EppException.UnimplementedCommandException;
|
||||
import google.registry.flows.Flow;
|
||||
import google.registry.flows.contact.ContactCheckFlow;
|
||||
import google.registry.flows.contact.ContactCreateFlow;
|
||||
import google.registry.flows.contact.ContactDeleteFlow;
|
||||
import google.registry.flows.contact.ContactInfoFlow;
|
||||
import google.registry.flows.contact.ContactTransferApproveFlow;
|
||||
import google.registry.flows.contact.ContactTransferCancelFlow;
|
||||
import google.registry.flows.contact.ContactTransferQueryFlow;
|
||||
import google.registry.flows.contact.ContactTransferRejectFlow;
|
||||
import google.registry.flows.contact.ContactTransferRequestFlow;
|
||||
import google.registry.flows.contact.ContactUpdateFlow;
|
||||
import google.registry.flows.domain.DomainCheckFlow;
|
||||
import google.registry.flows.domain.DomainClaimsCheckFlow;
|
||||
import google.registry.flows.domain.DomainCreateFlow;
|
||||
@@ -55,7 +46,6 @@ import google.registry.flows.poll.PollRequestFlow;
|
||||
import google.registry.flows.session.HelloFlow;
|
||||
import google.registry.flows.session.LoginFlow;
|
||||
import google.registry.flows.session.LogoutFlow;
|
||||
import google.registry.model.contact.ContactCommand;
|
||||
import google.registry.model.domain.DomainCommand;
|
||||
import google.registry.model.domain.launch.LaunchCheckExtension;
|
||||
import google.registry.model.domain.launch.LaunchCheckExtension.CheckType;
|
||||
@@ -202,11 +192,6 @@ public class FlowPicker {
|
||||
private static final FlowProvider RESOURCE_CRUD_FLOW_PROVIDER = new FlowProvider() {
|
||||
private final Map<Class<?>, Class<? extends Flow>> resourceCrudFlows =
|
||||
new ImmutableMap.Builder<Class<?>, Class<? extends Flow>>()
|
||||
.put(ContactCommand.Check.class, ContactCheckFlow.class)
|
||||
.put(ContactCommand.Create.class, ContactCreateFlow.class)
|
||||
.put(ContactCommand.Delete.class, ContactDeleteFlow.class)
|
||||
.put(ContactCommand.Info.class, ContactInfoFlow.class)
|
||||
.put(ContactCommand.Update.class, ContactUpdateFlow.class)
|
||||
.put(DomainCommand.Create.class, DomainCreateFlow.class)
|
||||
.put(DomainCommand.Delete.class, DomainDeleteFlow.class)
|
||||
.put(DomainCommand.Info.class, DomainInfoFlow.class)
|
||||
@@ -230,24 +215,6 @@ public class FlowPicker {
|
||||
new FlowProvider() {
|
||||
private final Table<Class<?>, TransferOp, Class<? extends Flow>> transferFlows =
|
||||
ImmutableTable.<Class<?>, TransferOp, Class<? extends Flow>>builder()
|
||||
.put(
|
||||
ContactCommand.Transfer.class,
|
||||
TransferOp.APPROVE,
|
||||
ContactTransferApproveFlow.class)
|
||||
.put(
|
||||
ContactCommand.Transfer.class,
|
||||
TransferOp.CANCEL,
|
||||
ContactTransferCancelFlow.class)
|
||||
.put(
|
||||
ContactCommand.Transfer.class, TransferOp.QUERY, ContactTransferQueryFlow.class)
|
||||
.put(
|
||||
ContactCommand.Transfer.class,
|
||||
TransferOp.REJECT,
|
||||
ContactTransferRejectFlow.class)
|
||||
.put(
|
||||
ContactCommand.Transfer.class,
|
||||
TransferOp.REQUEST,
|
||||
ContactTransferRequestFlow.class)
|
||||
.put(
|
||||
DomainCommand.Transfer.class,
|
||||
TransferOp.APPROVE,
|
||||
@@ -289,6 +256,17 @@ public class FlowPicker {
|
||||
if (innerCommand == null && !(eppInput.getCommandWrapper() instanceof Hello)) {
|
||||
throw new MissingCommandException();
|
||||
}
|
||||
if (innerCommand instanceof ResourceCommandWrapper resourceCommandWrapper) {
|
||||
ResourceCommand resourceCommand = resourceCommandWrapper.getResourceCommand();
|
||||
if (resourceCommand != null) {
|
||||
String wrapperName = innerCommand.getClass().getSimpleName();
|
||||
String commandName = resourceCommand.getClass().getSimpleName();
|
||||
if (!wrapperName.equals(commandName)) {
|
||||
throw new MismatchedCommandException(
|
||||
Ascii.toLowerCase(wrapperName), Ascii.toLowerCase(commandName));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Try the FlowProviders until we find a match. The order matters because it's possible to
|
||||
// match multiple FlowProviders and so more specific matches are tried first.
|
||||
for (FlowProvider flowProvider : FLOW_PROVIDERS) {
|
||||
@@ -313,4 +291,14 @@ public class FlowPicker {
|
||||
super("Command missing");
|
||||
}
|
||||
}
|
||||
|
||||
/** Command wrapper and inner resource command do not match. */
|
||||
static class MismatchedCommandException extends SyntaxErrorException {
|
||||
public MismatchedCommandException(String wrapperName, String commandName) {
|
||||
super(
|
||||
String.format(
|
||||
"EPP command wrapper <%s> does not match resource command <%s>",
|
||||
wrapperName, commandName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
package google.registry.flows.session;
|
||||
|
||||
import static com.google.common.collect.Sets.difference;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureName.PROHIBIT_CONTACT_OBJECTS_ON_LOGIN;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.CollectionUtils.nullToEmpty;
|
||||
|
||||
@@ -40,7 +39,6 @@ import google.registry.flows.TlsCredentials.BadRegistrarIpAddressException;
|
||||
import google.registry.flows.TlsCredentials.MissingRegistrarCertificateException;
|
||||
import google.registry.flows.TransportCredentials;
|
||||
import google.registry.flows.TransportCredentials.BadRegistrarPasswordException;
|
||||
import google.registry.model.common.FeatureFlag;
|
||||
import google.registry.model.eppcommon.ProtocolDefinition;
|
||||
import google.registry.model.eppcommon.ProtocolDefinition.ServiceExtension;
|
||||
import google.registry.model.eppinput.EppInput;
|
||||
@@ -120,9 +118,7 @@ public class LoginFlow implements MutatingFlow {
|
||||
Set<String> unsupportedObjectServices =
|
||||
difference(
|
||||
nullToEmpty(services.getObjectServices()),
|
||||
FeatureFlag.isActiveNow(PROHIBIT_CONTACT_OBJECTS_ON_LOGIN)
|
||||
? ProtocolDefinition.SUPPORTED_OBJECT_SERVICES
|
||||
: ProtocolDefinition.SUPPORTED_OBJECT_SERVICES_WITH_CONTACT);
|
||||
ProtocolDefinition.SUPPORTED_OBJECT_SERVICES);
|
||||
stopwatch.tick("LoginFlow difference unsupportedObjectServices");
|
||||
if (!unsupportedObjectServices.isEmpty()) {
|
||||
throw new UnimplementedObjectServiceException();
|
||||
|
||||
@@ -14,12 +14,15 @@
|
||||
|
||||
package google.registry.gcs;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.collect.Iterables.getLast;
|
||||
|
||||
import com.google.cloud.storage.Blob;
|
||||
import com.google.cloud.storage.BlobId;
|
||||
import com.google.cloud.storage.BlobInfo;
|
||||
import com.google.cloud.storage.Bucket;
|
||||
import com.google.cloud.storage.BucketInfo;
|
||||
import com.google.cloud.storage.Storage;
|
||||
import com.google.cloud.storage.Storage.BlobListOption;
|
||||
import com.google.cloud.storage.StorageException;
|
||||
@@ -33,12 +36,14 @@ import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.MediaType;
|
||||
import google.registry.config.CredentialModule.ApplicationDefaultCredential;
|
||||
import google.registry.util.GoogleCredentialsBundle;
|
||||
import google.registry.util.RegistryEnvironment;
|
||||
import jakarta.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.nio.channels.Channels;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import javax.annotation.CheckReturnValue;
|
||||
|
||||
/**
|
||||
@@ -50,6 +55,8 @@ public class GcsUtils implements Serializable {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private static final ConcurrentHashMap<String, Boolean> PAP_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
private static final ImmutableMap<String, MediaType> EXTENSIONS =
|
||||
new ImmutableMap.Builder<String, MediaType>()
|
||||
.put("ghostryde", MediaType.APPLICATION_BINARY)
|
||||
@@ -85,6 +92,7 @@ public class GcsUtils implements Serializable {
|
||||
/** Opens a GCS file for writing as an {@link OutputStream}, overwriting existing files. */
|
||||
@CheckReturnValue
|
||||
public OutputStream openOutputStream(BlobId blobId) {
|
||||
verifyPublicAccessPrevention(blobId.getBucket());
|
||||
return Channels.newOutputStream(storage().writer(createBlobInfo(blobId)));
|
||||
}
|
||||
|
||||
@@ -94,6 +102,7 @@ public class GcsUtils implements Serializable {
|
||||
*/
|
||||
@CheckReturnValue
|
||||
public OutputStream openOutputStream(BlobId blobId, ImmutableMap<String, String> metadata) {
|
||||
verifyPublicAccessPrevention(blobId.getBucket());
|
||||
return Channels.newOutputStream(
|
||||
storage().writer(BlobInfo.newBuilder(blobId).setMetadata(metadata).build()));
|
||||
}
|
||||
@@ -105,6 +114,7 @@ public class GcsUtils implements Serializable {
|
||||
|
||||
/** Creates a GCS file with the given byte contents and metadata, overwriting existing files. */
|
||||
public void createFromBytes(BlobInfo blobInfo, byte[] bytes) throws StorageException {
|
||||
verifyPublicAccessPrevention(blobInfo.getBucket());
|
||||
storage().create(blobInfo, bytes);
|
||||
}
|
||||
|
||||
@@ -120,6 +130,7 @@ public class GcsUtils implements Serializable {
|
||||
|
||||
/** Update file content type on existing GCS file */
|
||||
public void updateContentType(BlobId blobId, String contentType) throws StorageException {
|
||||
verifyPublicAccessPrevention(blobId.getBucket());
|
||||
if (existsAndNotEmpty(blobId)) {
|
||||
Blob blob = storage().get(blobId);
|
||||
blob.toBuilder().setContentType(contentType).build().update();
|
||||
@@ -154,12 +165,6 @@ public class GcsUtils implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the user defined metadata of a GCS file if the file exists, or an empty map. */
|
||||
public ImmutableMap<String, String> getMetadata(BlobId blobId) throws StorageException {
|
||||
Blob blob = storage().get(blobId);
|
||||
return blob == null ? ImmutableMap.of() : ImmutableMap.copyOf(blob.getMetadata());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link BlobInfo} of the given GCS file.
|
||||
*
|
||||
@@ -179,6 +184,37 @@ public class GcsUtils implements Serializable {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that Public Access Prevention (PAP) is enforced on the GCS bucket.
|
||||
*
|
||||
* @throws IllegalStateException if PAP is not ENFORCED.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void verifyPublicAccessPrevention(String bucketName) {
|
||||
if (RegistryEnvironment.get() != RegistryEnvironment.PRODUCTION) {
|
||||
return;
|
||||
}
|
||||
PAP_CACHE.computeIfAbsent(
|
||||
bucketName,
|
||||
name -> {
|
||||
Bucket bucket = storage().get(name);
|
||||
checkState(bucket != null, "Bucket %s does not exist", name);
|
||||
BucketInfo.PublicAccessPrevention pap =
|
||||
bucket.getIamConfiguration().getPublicAccessPrevention();
|
||||
checkState(
|
||||
pap == BucketInfo.PublicAccessPrevention.ENFORCED,
|
||||
"Public Access Prevention is not enforced on bucket %s. Current state: %s",
|
||||
name,
|
||||
pap);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static void clearPapCache() {
|
||||
PAP_CACHE.clear();
|
||||
}
|
||||
|
||||
// These two methods are needed to check whether serialization is done correctly in tests.
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
@@ -56,9 +56,9 @@ public final class GmailClient {
|
||||
private final InternetAddress outgoingEmailAddressWithUsername;
|
||||
private final InternetAddress replyToEmailAddress;
|
||||
|
||||
// TODO(b/510340944): make package private after feature is rolled out
|
||||
// TODO(b/510340944): drop sender info. Sender is determined by the Gmail credential owner.
|
||||
@Inject
|
||||
public GmailClient(
|
||||
GmailClient(
|
||||
Lazy<Gmail> gmail,
|
||||
Retrier retrier,
|
||||
@Config("isEmailSendingEnabled") boolean isEmailSendingEnabled,
|
||||
@@ -78,6 +78,20 @@ public final class GmailClient {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(b/510340944): Remove this experiment method
|
||||
public GmailClient(
|
||||
Lazy<Gmail> gmail,
|
||||
Retrier retrier,
|
||||
@Config("isEmailSendingEnabled") boolean isEmailSendingEnabled,
|
||||
@Config("replyToEmailAddress") InternetAddress replyToEmailAddress) {
|
||||
|
||||
this.gmail = gmail;
|
||||
this.retrier = retrier;
|
||||
this.isEmailSendingEnabled = isEmailSendingEnabled;
|
||||
this.replyToEmailAddress = replyToEmailAddress;
|
||||
this.outgoingEmailAddressWithUsername = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends {@code emailMessage} using {@link Gmail}.
|
||||
*/
|
||||
@@ -117,7 +131,9 @@ public final class GmailClient {
|
||||
try {
|
||||
MimeMessage msg =
|
||||
new MimeMessage(Session.getDefaultInstance(new Properties(), /* authenticator= */ null));
|
||||
msg.setFrom(this.outgoingEmailAddressWithUsername);
|
||||
if (this.outgoingEmailAddressWithUsername != null) {
|
||||
msg.setFrom(this.outgoingEmailAddressWithUsername);
|
||||
}
|
||||
msg.setReplyTo(
|
||||
new InternetAddress[] {emailMessage.replyToEmailAddress().orElse(replyToEmailAddress)});
|
||||
msg.addRecipients(
|
||||
|
||||
@@ -22,6 +22,7 @@ import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.keyring.api.Keyring;
|
||||
import google.registry.keyring.secretmanager.SecretManagerKeyring;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.util.Optional;
|
||||
|
||||
/** Dagger module for {@link Keyring} */
|
||||
@Module
|
||||
@@ -55,7 +56,7 @@ public abstract class KeyringModule {
|
||||
|
||||
@Provides
|
||||
@Config("valkeyCertificateAuthority")
|
||||
public static String provideValkeyCertificateAuthority(Keyring keyring) {
|
||||
return keyring.getValkeyCertificateAuthority();
|
||||
public static Optional<String> provideValkeyCertificateAuthority(Keyring keyring) {
|
||||
return Optional.ofNullable(keyring.getValkeyCertificateAuthority());
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user