1
0
mirror of https://github.com/google/nomulus synced 2026-05-27 18:20:32 +00:00

Compare commits

..

15 Commits

Author SHA1 Message Date
dependabot[bot]
fe710e5510 Bump postcss from 8.4.21 to 8.4.31 in /console-webapp (#2187)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.21 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.21...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-23 10:29:49 -04:00
sarahcaseybot
8f8ffe7020 Add a dryrun flag to configure_tld command (#2188)
This will be used for presubmit testing.
2023-10-20 16:16:05 -04:00
Lai Jiang
16e5018489 Update postcss version (#2185)
Per https://github.com/google/nomulus/security/dependabot/17
2023-10-20 13:30:40 -04:00
Lai Jiang
af303bd26f Remove URLFetch (#2181)
We previously needed to use URLFetch in some instances where TLS 1.3 is
required (mostly when connecting to ICANN servers),and the JDK-bundled SSL
engine that came with App Engine runtime did not support TLS 1.3.

It appears now that the Java 8 runtime on App Engine supports TLS 1.3
out of the box, which allows us to get rid of URLFetch, which depends on
App Engine APIs.

Also removed some redundant retry and logging logic, now that we know
the HTTP client behaves correctly.

TESTED=modified the CannedScriptExecutionAction and deployed to alpha, used the
new HTTP client to connect to the three URL endpoints that were
problematic before and confirmed that TLS connections can be established. HTTP
sessions were rejected in some cases when authentication failed, but
that was expected.
2023-10-19 14:51:51 -04:00
sarahcaseybot
bf3bb5d804 Add a Cloud Build job for syncing Tld configuration files from the internal repo with the database (#2174)
* Add a cloudbuild-tld-sync job

This job checks the Tld config files in the internal repo and syncs them with the actual Tld objects in the database using the configure_tld numulus command.

* Add the dockerfile and shell script

* Force the command

* Add comments

* add newline

* Create a separate copy of the job for each environment

* fix file name

* Fix indentation
2023-10-19 14:01:40 -04:00
dependabot[bot]
dcb16e05bd Bump @babel/traverse from 7.22.10 to 7.23.2 in /console-webapp (#2184)
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.22.10 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-19 11:46:02 -04:00
sarahcaseybot
2facedd60f Lower the isolation level for RefreshDnsForAllDomainsAction (#2182)
* Lower the isolation level for RefreshDnsForAllDomainsAction

This lowers the isolation level to TRANSACTION_REPEATABLE_READ which will hopefully allow the action to run the entire action without timing out on our larger TLDs.

* Unchange default config
2023-10-17 16:58:37 -04:00
Lai Jiang
b1ec81f054 Remove the wipeout job on QA (#2183) 2023-10-17 13:05:31 -04:00
gbrodman
779da518df Pass name/email/phone info to the new console front end (#2180) 2023-10-16 16:51:35 -04:00
sarahcaseybot
4f53ae0e89 Use reTransact when loading the cache for database objects (#2179)
Cache loads will likely always be inner transactions, if they have a transaction at all. Cache loads do not always call a transaction since they are only necessary if the cache is not fresh at the time it is called. Since the cache itself needs to decide whether or not a DB transaction is necessary, it should use the reTransact method to safely indicate that the isolation level of the outer transaction is what should be used.
2023-10-16 15:22:22 -04:00
gbrodman
da04caeea2 Don't check cert validation if we're not changing the certs in the console (#2178)
If the cert(s) are invalid or expired that's a problem, but that
shouldn't necessarily prevent us from changing other things. If we're
not changing the certs, leave them alone.
2023-10-16 13:37:57 -04:00
gbrodman
a63916b08e Refine error handling in RequestHandler and the console slightly (#2177)
If we don't explicitly handle random unexpected exceptions, the error
that the front end receives includes a big ole stacktrace, which is
unhelpful for regular users and possibly bad to expose. Instead, we
should provide a vague "something went wrong" message.

Separately, we can create a default SnackBar options and use that (we
want it longer than 1.5 seconds because that's pretty short).
2023-10-12 14:03:12 -04:00
Lai Jiang
36bd508bf9 Remove OAuthAuthenticationMechanism (#2171)
Also made some refactoring to various Auth related classes to clean up things a bit and make the logic less convoluted:

1. In Auth, remove AUTH_API_PUBLIC as it is only used by the WHOIS and EPP endpoints accessed by the proxy. Previously, the proxy relies on OAuth and its service account is not given admin role (in OAuth parlance), so we made them accessible by a public user, deferring authorization to the actions themselves. In practice, OAuth checks for allowlisted client IDs and only the proxy client ID was allowlisted, which effectively limited access to only the proxy anyway.

2. In AuthResult, expose the service account email if it is at APP level. RequestAuthenticator will print out the auth result and therefore log the email, making it easy to identify which account was used. This field is mutually exclusive to the user auth info field. As a result, the factory methods are refactored to explicitly create either APP or USER level auth result.

3. Completely re-wrote RequestAuthenticatorTest. Previously, the test mingled testing functionalities of the target class with testing how various authentication mechanisms work. Now they are cleanly decoupled, and each method in RequestAuthenticator is tested individually.

4. Removed nomulus-config-production-sample.yaml as it is vastly out of date.
2023-10-11 19:12:26 -04:00
Lai Jiang
bbdbfe85ed Remove the GAIA ID column from the User table (#2172)
The field has already been removed from the Java code base in #2170.
2023-10-11 12:47:48 -04:00
gbrodman
2a7e9a266a Fix minor alignment issue on console WHOIS page (#2166) 2023-10-11 09:25:05 -04:00
101 changed files with 3727 additions and 4388 deletions

View File

@@ -150,7 +150,7 @@
"ora": "5.4.1",
"parse5-html-rewriting-stream": "7.0.0",
"piscina": "3.2.0",
"postcss": "8.4.21",
"postcss": "8.4.31",
"postcss-loader": "7.0.2",
"resolve-url-loader": "5.0.0",
"rxjs": "6.6.7",
@@ -782,12 +782,12 @@
"dev": true
},
"node_modules/@babel/code-frame": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz",
"integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==",
"version": "7.22.13",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
"dev": true,
"dependencies": {
"@babel/highlight": "^7.22.10",
"@babel/highlight": "^7.22.13",
"chalk": "^2.4.2"
},
"engines": {
@@ -1072,36 +1072,36 @@
}
},
"node_modules/@babel/helper-environment-visitor": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz",
"integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==",
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-function-name": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz",
"integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
"dev": true,
"dependencies": {
"@babel/template": "^7.22.5",
"@babel/types": "^7.22.5"
"@babel/template": "^7.22.15",
"@babel/types": "^7.23.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-function-name/node_modules/@babel/template": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz",
"integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==",
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.22.5",
"@babel/parser": "^7.22.5",
"@babel/types": "^7.22.5"
"@babel/code-frame": "^7.22.13",
"@babel/parser": "^7.22.15",
"@babel/types": "^7.22.15"
},
"engines": {
"node": ">=6.9.0"
@@ -1287,9 +1287,9 @@
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz",
"integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==",
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
"dev": true,
"engines": {
"node": ">=6.9.0"
@@ -1361,12 +1361,12 @@
}
},
"node_modules/@babel/highlight": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz",
"integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==",
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
"dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.22.5",
"@babel/helper-validator-identifier": "^7.22.20",
"chalk": "^2.4.2",
"js-tokens": "^4.0.0"
},
@@ -1375,9 +1375,9 @@
}
},
"node_modules/@babel/parser": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.10.tgz",
"integrity": "sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
@@ -2597,19 +2597,19 @@
}
},
"node_modules/@babel/traverse": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.10.tgz",
"integrity": "sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==",
"version": "7.23.2",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.22.10",
"@babel/generator": "^7.22.10",
"@babel/helper-environment-visitor": "^7.22.5",
"@babel/helper-function-name": "^7.22.5",
"@babel/code-frame": "^7.22.13",
"@babel/generator": "^7.23.0",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0",
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/parser": "^7.22.10",
"@babel/types": "^7.22.10",
"@babel/parser": "^7.23.0",
"@babel/types": "^7.23.0",
"debug": "^4.1.0",
"globals": "^11.1.0"
},
@@ -2618,12 +2618,12 @@
}
},
"node_modules/@babel/traverse/node_modules/@babel/generator": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz",
"integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
"dev": true,
"dependencies": {
"@babel/types": "^7.22.10",
"@babel/types": "^7.23.0",
"@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
@@ -2659,13 +2659,13 @@
}
},
"node_modules/@babel/types": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz",
"integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.22.5",
"@babel/helper-validator-identifier": "^7.22.5",
"@babel/helper-validator-identifier": "^7.22.20",
"to-fast-properties": "^2.0.0"
},
"engines": {
@@ -11592,9 +11592,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.21",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
"integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"dev": true,
"funding": [
{
@@ -11604,10 +11604,14 @@
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"nanoid": "^3.3.4",
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
@@ -14459,7 +14463,7 @@
"ora": "5.4.1",
"parse5-html-rewriting-stream": "7.0.0",
"piscina": "3.2.0",
"postcss": "8.4.21",
"postcss": "8.4.31",
"postcss-loader": "7.0.2",
"resolve-url-loader": "5.0.0",
"rxjs": "6.6.7",
@@ -14882,12 +14886,12 @@
"dev": true
},
"@babel/code-frame": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz",
"integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==",
"version": "7.22.13",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
"dev": true,
"requires": {
"@babel/highlight": "^7.22.10",
"@babel/highlight": "^7.22.13",
"chalk": "^2.4.2"
}
},
@@ -15097,30 +15101,30 @@
}
},
"@babel/helper-environment-visitor": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz",
"integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==",
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
"dev": true
},
"@babel/helper-function-name": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz",
"integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
"dev": true,
"requires": {
"@babel/template": "^7.22.5",
"@babel/types": "^7.22.5"
"@babel/template": "^7.22.15",
"@babel/types": "^7.23.0"
},
"dependencies": {
"@babel/template": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz",
"integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==",
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.22.5",
"@babel/parser": "^7.22.5",
"@babel/types": "^7.22.5"
"@babel/code-frame": "^7.22.13",
"@babel/parser": "^7.22.15",
"@babel/types": "^7.22.15"
}
}
}
@@ -15258,9 +15262,9 @@
"dev": true
},
"@babel/helper-validator-identifier": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz",
"integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==",
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
"dev": true
},
"@babel/helper-validator-option": {
@@ -15318,20 +15322,20 @@
}
},
"@babel/highlight": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz",
"integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==",
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.22.5",
"@babel/helper-validator-identifier": "^7.22.20",
"chalk": "^2.4.2",
"js-tokens": "^4.0.0"
}
},
"@babel/parser": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.10.tgz",
"integrity": "sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
"dev": true
},
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
@@ -16160,30 +16164,30 @@
}
},
"@babel/traverse": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.10.tgz",
"integrity": "sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==",
"version": "7.23.2",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.22.10",
"@babel/generator": "^7.22.10",
"@babel/helper-environment-visitor": "^7.22.5",
"@babel/helper-function-name": "^7.22.5",
"@babel/code-frame": "^7.22.13",
"@babel/generator": "^7.23.0",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0",
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/parser": "^7.22.10",
"@babel/types": "^7.22.10",
"@babel/parser": "^7.23.0",
"@babel/types": "^7.23.0",
"debug": "^4.1.0",
"globals": "^11.1.0"
},
"dependencies": {
"@babel/generator": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz",
"integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
"dev": true,
"requires": {
"@babel/types": "^7.22.10",
"@babel/types": "^7.23.0",
"@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
@@ -16212,13 +16216,13 @@
}
},
"@babel/types": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz",
"integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
"dev": true,
"requires": {
"@babel/helper-string-parser": "^7.22.5",
"@babel/helper-validator-identifier": "^7.22.5",
"@babel/helper-validator-identifier": "^7.22.20",
"to-fast-properties": "^2.0.0"
}
},
@@ -23019,12 +23023,12 @@
}
},
"postcss": {
"version": "8.4.21",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
"integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"dev": true,
"requires": {
"nanoid": "^3.3.4",
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
}

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, ViewChild } from '@angular/core';
import { AfterViewInit, Component, ViewChild } from '@angular/core';
import { RegistrarService } from './registrar/registrar.service';
import { UserDataService } from './shared/services/userData.service';
import { GlobalLoaderService } from './shared/services/globalLoader.service';
@@ -24,7 +24,7 @@ import { MatSidenav } from '@angular/material/sidenav';
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
export class AppComponent implements AfterViewInit {
renderRouter: boolean = true;
@ViewChild('sidenav')

View File

@@ -48,6 +48,7 @@ import { DomainsWidgetComponent } from './home/widgets/domains-widget.component'
import { SettingsWidgetComponent } from './home/widgets/settings-widget.component';
import { UserDataService } from './shared/services/userData.service';
import WhoisComponent from './settings/whois/whois.component';
import { SnackBarModule } from './snackbar.module';
@NgModule({
declarations: [
@@ -79,6 +80,7 @@ import WhoisComponent from './settings/whois/whois.component';
FormsModule,
HttpClientModule,
MaterialModule,
SnackBarModule,
],
providers: [
BackendService,

View File

@@ -5,21 +5,23 @@
<mat-icon class="console-app__widget-icon">call</mat-icon>
<h1 class="console-app__widget-title">Contact Support</h1>
<h4 class="secondary-text text-center">
View Google Registry support email and phone information
Let us know if you have any questions
</h4>
</div>
<div class="console-app__widget_right">
<button mat-button color="primary" class="console-app__widget-link">
Give us a Call
</button>
<div class="console-app__widget-section-header">Give us a Call</div>
<p class="secondary-text">
Call Google Registry support at <b>+1 (404) 978 8419</b>
Call {{ userDataService.userData?.productName }} support at
<a href="tel:{{ userDataService.userData?.supportPhoneNumber }}">{{
userDataService.userData?.supportPhoneNumber
}}</a>
</p>
<button mat-button color="primary" class="console-app__widget-link">
Send us an Email
</button>
<div class="console-app__widget-section-header">Send us an Email</div>
<p class="secondary-text">
Email Google Registry at <b>support@google.com</b>
Email {{ userDataService.userData?.productName }} at
<a href="mailto:{{ userDataService.userData?.supportEmail }}">{{
userDataService.userData?.supportEmail
}}</a>
</p>
</div>
</div>

View File

@@ -13,11 +13,12 @@
// limitations under the License.
import { Component } from '@angular/core';
import { UserDataService } from 'src/app/shared/services/userData.service';
@Component({
selector: '[app-contact-widget]',
templateUrl: './contact-widget.component.html',
})
export class ContactWidgetComponent {
constructor() {}
constructor(public userDataService: UserDataService) {}
}

View File

@@ -89,8 +89,6 @@ export class RegistrarService implements GlobalLoader {
}
loadingTimeout() {
this._snackBar.open('Timeout loading registrars', undefined, {
duration: 1500,
});
this._snackBar.open('Timeout loading registrars');
}
}

View File

@@ -129,9 +129,7 @@ export class ContactDetailsDialogComponent {
operationObservable.subscribe({
complete: this.onCloseCallback.bind(this),
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error, undefined, {
duration: 1500,
});
this._snackBar.open(err.error);
},
});
}
@@ -175,9 +173,7 @@ export default class ContactComponent {
if (confirm(`Please confirm contact ${contact.name} delete`)) {
this.contactService.deleteContact(contact).subscribe({
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error, undefined, {
duration: 1500,
});
this._snackBar.open(err.error);
},
});
}

View File

@@ -64,9 +64,7 @@ export default class SecurityComponent {
this.resetDataSource();
},
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error, undefined, {
duration: 1500,
});
this._snackBar.open(err.error);
},
});
this.cancel();

View File

@@ -24,14 +24,14 @@
display: flex;
flex-wrap: wrap;
margin-bottom: 5px;
min-width: 450px;
min-width: 400px;
width: 50%;
max-width: 50%;
}
&__section-description {
display: inline-block;
margin-block-start: 1em;
min-width: 150px;
width: 160px;
}
&__section-form {
display: inline-block;

View File

@@ -61,9 +61,8 @@ export default class WhoisComponent {
this.resetDataSource();
},
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error, undefined, {
duration: 1500,
});
this._snackBar.open(err.error);
this.loading = false;
},
});
this.cancel();

View File

@@ -19,8 +19,11 @@ import { MatSnackBar } from '@angular/material/snack-bar';
import { GlobalLoader, GlobalLoaderService } from './globalLoader.service';
export interface UserData {
isAdmin: boolean;
globalRole: string;
isAdmin: boolean;
productName: string;
supportEmail: string;
supportPhoneNumber: string;
technicalDocsUrl: string;
}
@@ -49,8 +52,6 @@ export class UserDataService implements GlobalLoader {
}
loadingTimeout() {
this._snackBar.open('Timeout loading user data', undefined, {
duration: 1500,
});
this._snackBar.open('Timeout loading user data');
}
}

View File

@@ -0,0 +1,24 @@
// Copyright 2023 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { NgModule } from '@angular/core';
import { MAT_SNACK_BAR_DEFAULT_OPTIONS } from '@angular/material/snack-bar';
/** Provides a default set of options for the snack bar. */
@NgModule({
providers: [
{ provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, useValue: { duration: 5000 } },
],
})
export class SnackBarModule {}

View File

@@ -47,6 +47,10 @@ body {
min-width: auto !important;
height: min-content !important;
}
&-section-header {
font-weight: 500;
color: var(--primary) !important;
}
&-title {
color: var(--primary) !important;
text-align: center;

View File

@@ -43,6 +43,12 @@ public class BatchModule {
public static final String PARAM_DRY_RUN = "dryRun";
public static final String PARAM_FAST = "fast";
@Provides
@Parameter("url")
static String provideUrl(HttpServletRequest req) {
return extractRequiredParameter(req, "url");
}
@Provides
@Parameter("jobName")
static Optional<String> provideJobName(HttpServletRequest req) {

View File

@@ -14,26 +14,20 @@
package google.registry.batch;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.POST;
import static google.registry.util.RegistrarUtils.normalizeRegistrarId;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryConfig.Config;
import google.registry.groups.GmailClient;
import google.registry.groups.GroupsConnection;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.Response;
import google.registry.request.UrlConnectionService;
import google.registry.request.UrlConnectionUtils;
import google.registry.request.auth.Auth;
import google.registry.util.EmailMessage;
import java.io.IOException;
import java.util.Set;
import java.net.URL;
import javax.inject.Inject;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.net.ssl.HttpsURLConnection;
/**
* Action that executes a canned script specified by the caller.
@@ -50,88 +44,45 @@ import javax.mail.internet.InternetAddress;
@Action(
service = Action.Service.BACKEND,
path = "/_dr/task/executeCannedScript",
method = POST,
method = {POST, GET},
automaticallyPrintOk = true,
auth = Auth.AUTH_API_ADMIN)
public class CannedScriptExecutionAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final GroupsConnection groupsConnection;
private final GmailClient gmailClient;
private final InternetAddress senderAddress;
private final InternetAddress recipientAddress;
private final String gSuiteDomainName;
@Inject UrlConnectionService urlConnectionService;
@Inject Response response;
@Inject
CannedScriptExecutionAction(
GroupsConnection groupsConnection,
GmailClient gmailClient,
@Config("projectId") String projectId,
@Config("gSuiteDomainName") String gSuiteDomainName,
@Config("newAlertRecipientEmailAddress") InternetAddress recipientAddress) {
this.groupsConnection = groupsConnection;
this.gmailClient = gmailClient;
this.gSuiteDomainName = gSuiteDomainName;
try {
this.senderAddress = new InternetAddress(String.format("%s@%s", projectId, gSuiteDomainName));
} catch (AddressException e) {
throw new RuntimeException(e);
}
this.recipientAddress = recipientAddress;
logger.atInfo().log("Sender:%s; Recipient: %s.", this.senderAddress, this.recipientAddress);
}
@Parameter("url")
String url;
@Inject
CannedScriptExecutionAction() {}
@Override
public void run() {
Integer responseCode = null;
String responseContent = null;
try {
// Invoke canned scripts here.
checkGroupApi();
EmailMessage message = createEmail();
this.gmailClient.sendEmail(message);
logger.atInfo().log("Finished running scripts.");
} catch (Throwable t) {
logger.atWarning().withCause(t).log("Error executing scripts.");
throw new RuntimeException("Execution failed.");
}
}
// Checks if Directory and GroupSettings still work after GWorkspace changes.
void checkGroupApi() {
ImmutableList<Registrar> registrars =
Streams.stream(Registrar.loadAllCached())
.filter(registrar -> registrar.isLive() && registrar.getType() == Registrar.Type.REAL)
.collect(toImmutableList());
logger.atInfo().log("Found %s registrars.", registrars.size());
for (Registrar registrar : registrars) {
for (final RegistrarPoc.Type type : RegistrarPoc.Type.values()) {
String groupKey =
String.format(
"%s-%s-contacts@%s",
normalizeRegistrarId(registrar.getRegistrarId()),
type.getDisplayName(),
gSuiteDomainName);
try {
Set<String> currentMembers = groupsConnection.getMembersOfGroup(groupKey);
logger.atInfo().log("%s has %s members.", groupKey, currentMembers.size());
// One success is enough for validation.
return;
} catch (IOException e) {
logger.atWarning().withCause(e).log("Failed to check %s", groupKey);
}
logger.atInfo().log("Connecting to: %s", url);
HttpsURLConnection connection =
(HttpsURLConnection) urlConnectionService.createConnection(new URL(url));
responseCode = connection.getResponseCode();
logger.atInfo().log("Code: %d", responseCode);
logger.atInfo().log("Headers: %s", connection.getHeaderFields());
responseContent = new String(UrlConnectionUtils.getResponseBytes(connection), UTF_8);
logger.atInfo().log("Response: %s", responseContent);
} catch (Exception e) {
logger.atWarning().withCause(e).log("Connection to %s failed", url);
throw new RuntimeException(e);
} finally {
if (responseCode != null) {
response.setStatus(responseCode);
}
if (responseContent != null) {
response.setPayload(responseContent);
}
}
logger.atInfo().log("Finished checking GroupApis.");
}
EmailMessage createEmail() {
return EmailMessage.newBuilder()
.setFrom(senderAddress)
.setSubject("Test: Please ignore<eom>.")
.setRecipients(ImmutableList.of(recipientAddress))
.setBody("Sent from Nomulus through Google Workspace.")
.build();
}
}

View File

@@ -1174,44 +1174,6 @@ public final class RegistryConfig {
return CONFIG_SETTINGS.get();
}
/**
* Provides the OAuth scopes that authentication logic should detect on access tokens.
*
* <p>This list should be a superset of the required OAuth scope set provided below. Note that
* ideally, this setting would not be required and all scopes on an access token would be
* detected automatically, but that is not the case due to the way {@code OAuthService} works.
*
* <p>This is an independent setting from the required OAuth scopes (below) to support use cases
* where certain actions require some additional scope (e.g. access to a user's Google Drive)
* but that scope shouldn't be required for authentication alone; in that case the Drive scope
* would be specified only for this setting, allowing that action to check for its presence.
*/
@Provides
@Config("availableOauthScopes")
public static ImmutableSet<String> provideAvailableOauthScopes(RegistryConfigSettings config) {
return ImmutableSet.copyOf(config.auth.availableOauthScopes);
}
/**
* Provides the OAuth scopes that are required for authenticating successfully.
*
* <p>This set contains the scopes which must be present to authenticate a user. It should be a
* subset of the scopes we request from the OAuth interface, provided above.
*
* <p>If we feel the need, we could define additional fixed scopes, similar to the Java remote
* API, which requires at least one of:
*
* <ul>
* <li>{@code https://www.googleapis.com/auth/appengine.apis}
* <li>{@code https://www.googleapis.com/auth/cloud-platform}
* </ul>
*/
@Provides
@Config("requiredOauthScopes")
public static ImmutableSet<String> provideRequiredOauthScopes(RegistryConfigSettings config) {
return ImmutableSet.copyOf(config.auth.requiredOauthScopes);
}
/**
* Provides service account email addresses allowed to authenticate with the app at {@link
* google.registry.request.auth.AuthSettings.AuthLevel#APP} level.
@@ -1223,13 +1185,6 @@ public final class RegistryConfig {
return ImmutableSet.copyOf(config.auth.allowedServiceAccountEmails);
}
/** Provides the allowed OAuth client IDs (could be multibinding). */
@Provides
@Config("allowedOauthClientIds")
public static ImmutableSet<String> provideAllowedOauthClientIds(RegistryConfigSettings config) {
return ImmutableSet.copyOf(config.auth.allowedOauthClientIds);
}
@Provides
@Config("oauthClientId")
public static String provideOauthClientId(RegistryConfigSettings config) {

View File

@@ -58,9 +58,6 @@ public class RegistryConfigSettings {
/** Configuration options for authenticating users. */
public static class Auth {
public List<String> availableOauthScopes;
public List<String> requiredOauthScopes;
public List<String> allowedOauthClientIds;
public List<String> allowedServiceAccountEmails;
public String oauthClientId;
}

View File

@@ -304,24 +304,6 @@ caching:
# Note: Only allowedServiceAccountEmails and oauthClientId should be configured.
# Other fields are related to OAuth-based authentication and will be removed.
auth:
# Deprecated: Use OIDC-based auth instead. This field is for OAuth-based auth.
# OAuth scopes to detect on access tokens. Superset of requiredOauthScopes.
availableOauthScopes:
- https://www.googleapis.com/auth/userinfo.email
# Deprecated: Use OIDC-based auth instead. This field is for OAuth-based auth.
# OAuth scopes required for authenticating. Subset of availableOauthScopes.
requiredOauthScopes:
- https://www.googleapis.com/auth/userinfo.email
# Deprecated: Use OIDC-based auth instead. This field is for OAuth-based auth.
# OAuth client IDs that are allowed to authenticate and communicate with
# backend services, e.g. nomulus tool, EPP proxy, etc. The value in
# registryTool.clientId field should be included in this list. Client IDs are
# typically of the format
# numbers-alphanumerics.apps.googleusercontent.com
allowedOauthClientIds: []
# Service accounts (e.g. default service account, account used by Cloud
# Scheduler) allowed to send authenticated requests.
allowedServiceAccountEmails:

View File

@@ -1,76 +0,0 @@
# This is a sample production config (to be deployed in the WEB-INF directory).
# This is the same as what Google Registry runs in production, except with
# placeholders for Google-specific settings.
gcpProject:
projectId: placeholder
# Set to true if running against local servers (localhost)
isLocal: false
# The "<service>-dot-" prefix is used on the project ID in this URL in order
# to get around an issue with double-wildcard SSL certs.
defaultServiceUrl: https://domain-registry-placeholder.appspot.com
backendServiceUrl: https://backend-dot-domain-registry-placeholder.appspot.com
toolsServiceUrl: https://tools-dot-domain-registry-placeholder.appspot.com
pubapiServiceUrl: https://pubapi-dot-domain-registry-placeholder.appspot.com
gSuite:
domainName: placeholder
outgoingEmailDisplayName: placeholder
outgoingEmailAddress: placeholder
adminAccountEmailAddress: placeholder
supportGroupEmailAddress: placeholder
registryPolicy:
contactAndHostRoidSuffix: placeholder
productName: placeholder
greetingServerId: placeholder
registrarChangesNotificationEmailAddresses:
- placeholder
- placeholder
defaultRegistrarWhoisServer: placeholder
tmchCaMode: PRODUCTION
tmchCrlUrl: http://crl.icann.org/tmch.crl
tmchMarksDbUrl: https://ry.marksdb.org
checkApiServletClientId: placeholder
registryAdminClientId: placeholder
whoisDisclaimer: |
multi-line
placeholder
icannReporting:
icannTransactionsReportingUploadUrl: https://ry-api.icann.org/report/registrar-transactions
icannActivityReportingUploadUrl: https://ry-api.icann.org/report/registry-functions-activity
oAuth:
allowedOauthClientIds:
- placeholder.apps.googleusercontent.com
- placeholder-for-proxy
rde:
reportUrlPrefix: https://ry-api.icann.org/report/registry-escrow-report
uploadUrl: sftp://placeholder@sftpipm2.ironmountain.com/Outbox
sshIdentityEmailAddress: placeholder
registrarConsole:
logoFilename: placeholder
supportPhoneNumber: placeholder
supportEmailAddress: placeholder
announcementsEmailAddress: placeholder
integrationEmailAddress: placeholder
technicalDocsUrl: https://drive.google.com/drive/folders/placeholder
misc:
sheetExportId: placeholder
cloudDns:
rootUrl: null
servicePath: null
keyring:
activeKeyring: KMS
kms:
projectId: placeholder
registryTool:
clientId: placeholder.apps.googleusercontent.com
clientSecret: placeholder

View File

@@ -59,13 +59,4 @@
</description>
<schedule>7 3 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/task/wipeOutCloudSql]]></url>
<name>wipeOutCloudSql</name>
<description>
This job runs an action that deletes all data in Cloud SQL.
</description>
<schedule>7 3 * * 6</schedule>
</task>
</entries>

View File

@@ -29,7 +29,7 @@ import javax.servlet.http.HttpSession;
service = Action.Service.DEFAULT,
path = "/_dr/epp",
method = Method.POST,
auth = Auth.AUTH_API_PUBLIC)
auth = Auth.AUTH_API_ADMIN)
public class EppTlsAction implements Runnable {
@Inject @Payload byte[] inputXmlBytes;

View File

@@ -305,14 +305,14 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
new CacheLoader<VKey<AllocationToken>, Optional<AllocationToken>>() {
@Override
public Optional<AllocationToken> load(VKey<AllocationToken> key) {
return tm().transact(() -> tm().loadByKeyIfPresent(key));
return tm().reTransact(() -> tm().loadByKeyIfPresent(key));
}
@Override
public Map<VKey<AllocationToken>, Optional<AllocationToken>> loadAll(
Iterable<? extends VKey<AllocationToken>> keys) {
ImmutableSet<VKey<AllocationToken>> keySet = ImmutableSet.copyOf(keys);
return tm().transact(
return tm().reTransact(
() ->
keySet.stream()
.collect(

View File

@@ -200,7 +200,11 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
/** A caching {@link Supplier} of a registrarId to {@link Registrar} map. */
private static final Supplier<ImmutableMap<String, Registrar>> CACHE_BY_REGISTRAR_ID =
memoizeWithShortExpiration(() -> Maps.uniqueIndex(loadAll(), Registrar::getRegistrarId));
memoizeWithShortExpiration(
() ->
Maps.uniqueIndex(
tm().reTransact(() -> tm().loadAllOf(Registrar.class)),
Registrar::getRegistrarId));
/**
* Unique registrar client id. Must conform to "clIDType" as defined in RFC5730.

View File

@@ -28,7 +28,7 @@ public class SignedMarkRevocationListDao {
/** Loads the {@link SignedMarkRevocationList}. */
static SignedMarkRevocationList load() {
Optional<SignedMarkRevocationList> smdrl =
tm().transact(
tm().reTransact(
() -> {
Long revisionId =
tm().query("SELECT MAX(revisionId) FROM SignedMarkRevocationList", Long.class)

View File

@@ -233,7 +233,8 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
new CacheLoader<String, Tld>() {
@Override
public Tld load(final String tld) {
return tm().transact(() -> tm().loadByKeyIfPresent(createVKey(tld))).orElse(null);
return tm().reTransact(() -> tm().loadByKeyIfPresent(createVKey(tld)))
.orElse(null);
}
@Override
@@ -241,7 +242,7 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
ImmutableMap<String, VKey<Tld>> keysMap =
toMap(ImmutableSet.copyOf(tlds), Tld::createVKey);
Map<VKey<? extends Tld>, Tld> entities =
tm().transact(() -> tm().loadByKeysIfPresent(keysMap.values()));
tm().reTransact(() -> tm().loadByKeysIfPresent(keysMap.values()));
return Maps.transformEntries(keysMap, (k, v) -> entities.getOrDefault(v, null));
}
});

View File

@@ -56,7 +56,7 @@ public final class Tlds {
private static Supplier<ImmutableMap<String, TldType>> createFreshCache() {
return memoizeWithShortExpiration(
() ->
tm().transact(
tm().reTransact(
() -> {
EntityManager entityManager = tm().getEntityManager();
Stream<?> resultStream =

View File

@@ -43,8 +43,6 @@ import google.registry.rde.JSchModule;
import google.registry.request.Modules.GsonModule;
import google.registry.request.Modules.NetHttpTransportModule;
import google.registry.request.Modules.UrlConnectionServiceModule;
import google.registry.request.Modules.UrlFetchServiceModule;
import google.registry.request.Modules.UrlFetchTransportModule;
import google.registry.request.Modules.UserServiceModule;
import google.registry.request.auth.AuthModule;
import google.registry.util.UtilsModule;
@@ -80,8 +78,6 @@ import javax.inject.Singleton;
SheetsServiceModule.class,
StackdriverModule.class,
UrlConnectionServiceModule.class,
UrlFetchServiceModule.class,
UrlFetchTransportModule.class,
UserServiceModule.class,
VoidDnsWriterModule.class,
UtilsModule.class

View File

@@ -14,22 +14,26 @@
package google.registry.rdap;
import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_OK;
import static com.google.common.net.HttpHeaders.ACCEPT_ENCODING;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpTransport;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import com.google.common.io.ByteStreams;
import google.registry.model.registrar.Registrar;
import google.registry.request.Action;
import google.registry.request.HttpException.InternalServerErrorException;
import google.registry.request.UrlConnectionService;
import google.registry.request.UrlConnectionUtils;
import google.registry.request.auth.Auth;
import google.registry.util.UrlConnectionException;
import java.io.IOException;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.GeneralSecurityException;
import javax.inject.Inject;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
@@ -41,9 +45,9 @@ import org.apache.commons.csv.CSVRecord;
* <p>This will update ALL the REAL registrars. If a REAL registrar doesn't have an RDAP entry in
* MoSAPI, we'll delete any BaseUrls it has.
*
* <p>The ICANN base website that provides this information can be found at
* https://www.iana.org/assignments/registrar-ids/registrar-ids.xhtml. The provided CSV endpoint
* requires no authentication.
* <p>The ICANN base website that provides this information can be found at <a
* href=https://www.iana.org/assignments/registrar-ids/registrar-ids.xhtml>here</a>. The provided
* CSV endpoint requires no authentication.
*/
@Action(
service = Action.Service.BACKEND,
@@ -52,22 +56,26 @@ import org.apache.commons.csv.CSVRecord;
auth = Auth.AUTH_API_ADMIN)
public final class UpdateRegistrarRdapBaseUrlsAction implements Runnable {
private static final GenericUrl RDAP_IDS_URL =
new GenericUrl("https://www.iana.org/assignments/registrar-ids/registrar-ids-1.csv");
private static final String RDAP_IDS_URL =
"https://www.iana.org/assignments/registrar-ids/registrar-ids-1.csv";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Inject HttpTransport httpTransport;
@Inject UrlConnectionService urlConnectionService;
@Inject
UpdateRegistrarRdapBaseUrlsAction() {}
@Override
public void run() {
ImmutableMap<String, String> ianaIdsToUrls = getIanaIdsToUrls();
tm().transact(() -> processAllRegistrars(ianaIdsToUrls));
try {
ImmutableMap<String, String> ianaIdsToUrls = getIanaIdsToUrls();
tm().transact(() -> processAllRegistrars(ianaIdsToUrls));
} catch (Exception e) {
throw new InternalServerErrorException("Error when retrieving RDAP base URL CSV file", e);
}
}
private void processAllRegistrars(ImmutableMap<String, String> ianaIdsToUrls) {
private static void processAllRegistrars(ImmutableMap<String, String> ianaIdsToUrls) {
int nonUpdatedRegistrars = 0;
for (Registrar registrar : Registrar.loadAll()) {
// Only update REAL registrars
@@ -95,23 +103,28 @@ public final class UpdateRegistrarRdapBaseUrlsAction implements Runnable {
logger.atInfo().log("No change in RDAP base URLs for %d registrars", nonUpdatedRegistrars);
}
private ImmutableMap<String, String> getIanaIdsToUrls() {
private ImmutableMap<String, String> getIanaIdsToUrls()
throws IOException, GeneralSecurityException {
CSVParser csv;
HttpURLConnection connection = urlConnectionService.createConnection(new URL(RDAP_IDS_URL));
// Explictly set the accepted encoding, as we know Brotli causes us problems when talking to
// ICANN.
connection.setRequestProperty(ACCEPT_ENCODING, "gzip");
String csvString;
try {
HttpRequest request = httpTransport.createRequestFactory().buildGetRequest(RDAP_IDS_URL);
// AppEngine might insert accept-encodings for us if we use the default gzip, so remove it
request.getHeaders().setAcceptEncoding(null);
HttpResponse response = request.execute();
String csvString = new String(ByteStreams.toByteArray(response.getContent()), UTF_8);
csv =
CSVFormat.Builder.create(CSVFormat.DEFAULT)
.setHeader()
.setSkipHeaderRecord(true)
.build()
.parse(new StringReader(csvString));
} catch (IOException e) {
throw new RuntimeException("Error when retrieving RDAP base URL CSV file", e);
if (connection.getResponseCode() != STATUS_CODE_OK) {
throw new UrlConnectionException("Failed to load RDAP base URLs from ICANN", connection);
}
csvString = new String(UrlConnectionUtils.getResponseBytes(connection), UTF_8);
} finally {
connection.disconnect();
}
csv =
CSVFormat.Builder.create(CSVFormat.DEFAULT)
.setHeader()
.setSkipHeaderRecord(true)
.build()
.parse(new StringReader(csvString));
ImmutableMap.Builder<String, String> result = new ImmutableMap.Builder<>();
for (CSVRecord record : csv) {
String ianaIdentifierString = record.get("ID");

View File

@@ -14,25 +14,23 @@
package google.registry.rde;
import static com.google.appengine.api.urlfetch.FetchOptions.Builder.validateCertificate;
import static com.google.appengine.api.urlfetch.HTTPMethod.PUT;
import static com.google.common.io.BaseEncoding.base64;
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_BAD_REQUEST;
import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_OK;
import static google.registry.request.UrlConnectionUtils.getResponseBytes;
import static google.registry.request.UrlConnectionUtils.setBasicAuth;
import static google.registry.request.UrlConnectionUtils.setPayload;
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import static java.nio.charset.StandardCharsets.UTF_8;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import com.google.appengine.api.urlfetch.HTTPHeader;
import com.google.appengine.api.urlfetch.HTTPRequest;
import com.google.api.client.http.HttpMethods;
import com.google.appengine.api.urlfetch.HTTPResponse;
import com.google.appengine.api.urlfetch.URLFetchService;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import google.registry.config.RegistryConfig.Config;
import google.registry.keyring.api.KeyModule.Key;
import google.registry.request.HttpException.InternalServerErrorException;
import google.registry.util.Retrier;
import google.registry.request.UrlConnectionService;
import google.registry.util.UrlConnectionException;
import google.registry.xjc.XjcXmlTransformer;
import google.registry.xjc.iirdea.XjcIirdeaResponseElement;
import google.registry.xjc.iirdea.XjcIirdeaResult;
@@ -40,10 +38,11 @@ import google.registry.xjc.rdeheader.XjcRdeHeader;
import google.registry.xjc.rdereport.XjcRdeReportReport;
import google.registry.xml.XmlException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.Arrays;
import java.security.GeneralSecurityException;
import javax.inject.Inject;
/**
@@ -59,49 +58,47 @@ public class RdeReporter {
* @see <a href="http://tools.ietf.org/html/draft-lozano-icann-registry-interfaces-05#section-4">
* ICANN Registry Interfaces - Interface details</a>
*/
private static final String REPORT_MIME = "text/xml";
private static final MediaType MEDIA_TYPE = MediaType.XML_UTF_8;
@Inject Retrier retrier;
@Inject URLFetchService urlFetchService;
@Inject UrlConnectionService urlConnectionService;
@Inject @Config("rdeReportUrlPrefix") String reportUrlPrefix;
@Inject @Key("icannReportingPassword") String password;
@Inject RdeReporter() {}
/** Uploads {@code reportBytes} to ICANN. */
public void send(byte[] reportBytes) throws XmlException {
XjcRdeReportReport report = XjcXmlTransformer.unmarshal(
XjcRdeReportReport.class, new ByteArrayInputStream(reportBytes));
public void send(byte[] reportBytes) throws XmlException, GeneralSecurityException, IOException {
XjcRdeReportReport report =
XjcXmlTransformer.unmarshal(
XjcRdeReportReport.class, new ByteArrayInputStream(reportBytes));
XjcRdeHeader header = report.getHeader().getValue();
// Send a PUT request to ICANN's HTTPS server.
URL url = makeReportUrl(header.getTld(), report.getId());
String username = header.getTld() + "_ry";
String token = base64().encode(String.format("%s:%s", username, password).getBytes(UTF_8));
final HTTPRequest req = new HTTPRequest(url, PUT, validateCertificate().setDeadline(60d));
req.addHeader(new HTTPHeader(CONTENT_TYPE, REPORT_MIME));
req.addHeader(new HTTPHeader(AUTHORIZATION, "Basic " + token));
req.setPayload(reportBytes);
logger.atInfo().log("Sending report:\n%s", new String(reportBytes, UTF_8));
HTTPResponse rsp =
retrier.callWithRetry(
() -> {
HTTPResponse rsp1 = urlFetchService.fetch(req);
int responseCode = rsp1.getResponseCode();
if (responseCode != SC_OK && responseCode != SC_BAD_REQUEST) {
logger.atSevere().log(
"Failure when trying to PUT RDE report to ICANN server: %d\n%s",
responseCode, Arrays.toString(rsp1.getContent()));
throw new RuntimeException("Error uploading deposits to ICANN");
}
return rsp1;
},
SocketTimeoutException.class);
HttpURLConnection connection = urlConnectionService.createConnection(url);
connection.setRequestMethod(HttpMethods.PUT);
setBasicAuth(connection, username, password);
setPayload(connection, reportBytes, MEDIA_TYPE.toString());
int responseCode;
byte[] responseBytes;
// Ensure the XML response is valid. The EPP result code would not be 1000 if we get an
// SC_BAD_REQUEST as the HTTP response code.
XjcIirdeaResult result = parseResult(rsp.getContent());
if (result.getCode().getValue() != 1000) {
try {
responseCode = connection.getResponseCode();
if (responseCode != STATUS_CODE_OK && responseCode != STATUS_CODE_BAD_REQUEST) {
logger.atWarning().log("Connection to RDE report server failed: %d", responseCode);
throw new UrlConnectionException("PUT failed", connection);
}
responseBytes = getResponseBytes(connection);
} finally {
connection.disconnect();
}
// We know that an HTTP 200 response can only contain a result code of
// 1000 (i. e. success), there is no need to parse it.
if (responseCode != STATUS_CODE_OK) {
XjcIirdeaResult result = parseResult(responseBytes);
logger.atWarning().log(
"Rejected when trying to PUT RDE report to ICANN server: %d %s\n%s",
result.getCode().getValue(), result.getMsg(), result.getDescription());
@@ -116,10 +113,11 @@ public class RdeReporter {
* href="http://tools.ietf.org/html/draft-lozano-icann-registry-interfaces-05#section-4.1">
* ICANN Registry Interfaces - IIRDEA Result Object</a>
*/
private XjcIirdeaResult parseResult(byte[] responseBytes) throws XmlException {
private static XjcIirdeaResult parseResult(byte[] responseBytes) throws XmlException {
logger.atInfo().log("Received response:\n%s", new String(responseBytes, UTF_8));
XjcIirdeaResponseElement response = XjcXmlTransformer.unmarshal(
XjcIirdeaResponseElement.class, new ByteArrayInputStream(responseBytes));
XjcIirdeaResponseElement response =
XjcXmlTransformer.unmarshal(
XjcIirdeaResponseElement.class, new ByteArrayInputStream(responseBytes));
return response.getResult();
}

View File

@@ -14,33 +14,31 @@
package google.registry.reporting.icann;
import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_BAD_REQUEST;
import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_OK;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.net.MediaType.CSV_UTF_8;
import static google.registry.model.tld.Tlds.assertTldExists;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.api.client.http.ByteArrayContent;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.HttpStatusCodes;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.HttpMethods;
import com.google.common.base.Ascii;
import com.google.common.base.Splitter;
import com.google.common.flogger.FluentLogger;
import com.google.common.io.BaseEncoding;
import com.google.common.io.ByteStreams;
import google.registry.config.RegistryConfig.Config;
import google.registry.keyring.api.KeyModule.Key;
import google.registry.reporting.icann.IcannReportingModule.ReportType;
import google.registry.request.UrlConnectionService;
import google.registry.request.UrlConnectionUtils;
import google.registry.xjc.XjcXmlTransformer;
import google.registry.xjc.iirdea.XjcIirdeaResponseElement;
import google.registry.xjc.iirdea.XjcIirdeaResult;
import google.registry.xml.XmlException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.util.List;
import javax.inject.Inject;
import org.joda.time.YearMonth;
@@ -62,78 +60,64 @@ public class IcannHttpReporter {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Inject HttpTransport httpTransport;
@Inject @Key("icannReportingPassword") String password;
@Inject @Config("icannTransactionsReportingUploadUrl") String icannTransactionsUrl;
@Inject @Config("icannActivityReportingUploadUrl") String icannActivityUrl;
@Inject IcannHttpReporter() {}
@Inject UrlConnectionService urlConnectionService;
@Inject
@Key("icannReportingPassword")
String password;
@Inject
@Config("icannTransactionsReportingUploadUrl")
String icannTransactionsUrl;
@Inject
@Config("icannActivityReportingUploadUrl")
String icannActivityUrl;
@Inject
IcannHttpReporter() {}
/** Uploads {@code reportBytes} to ICANN, returning whether or not it succeeded. */
public boolean send(byte[] reportBytes, String reportFilename) throws XmlException, IOException {
public boolean send(byte[] reportBytes, String reportFilename)
throws GeneralSecurityException, XmlException, IOException {
validateReportFilename(reportFilename);
GenericUrl uploadUrl = new GenericUrl(makeUrl(reportFilename));
HttpRequest request =
httpTransport
.createRequestFactory()
.buildPutRequest(uploadUrl, new ByteArrayContent(CSV_UTF_8.toString(), reportBytes));
HttpHeaders headers = request.getHeaders();
headers.setBasicAuthentication(getTld(reportFilename) + "_ry", password);
headers.setContentType(CSV_UTF_8.toString());
request.setHeaders(headers);
request.setFollowRedirects(false);
request.setThrowExceptionOnExecuteError(false);
HttpResponse response = null;
URL uploadUrl = makeUrl(reportFilename);
logger.atInfo().log(
"Sending report to %s with content length %d.",
uploadUrl, request.getContent().getLength());
boolean success = true;
"Sending report to %s with content length %d.", uploadUrl, reportBytes.length);
HttpURLConnection connection = urlConnectionService.createConnection(uploadUrl);
connection.setRequestMethod(HttpMethods.PUT);
UrlConnectionUtils.setBasicAuth(connection, getTld(reportFilename) + "_ry", password);
UrlConnectionUtils.setPayload(connection, reportBytes, CSV_UTF_8.toString());
connection.setInstanceFollowRedirects(false);
int responseCode;
byte[] content;
try {
response = request.execute();
// Only responses with a 200 or 400 status have a body. For everything else, throw so that
// the caller catches it and prints the stack trace.
if (response.getStatusCode() != HttpStatusCodes.STATUS_CODE_OK
&& response.getStatusCode() != HttpStatusCodes.STATUS_CODE_BAD_REQUEST) {
throw new HttpResponseException(response);
}
byte[] content;
try {
content = ByteStreams.toByteArray(response.getContent());
} finally {
response.getContent().close();
}
logger.atInfo().log(
"Received response code %d\n\n"
+ "Response headers: %s\n\n"
+ "Response content in UTF-8: %s\n\n"
+ "Response content in HEX: %s",
response.getStatusCode(),
response.getHeaders(),
new String(content, UTF_8),
BaseEncoding.base16().encode(content));
// For reasons unclear at the moment, when we parse the response content using UTF-8 we get
// garbled texts. Since we know that an HTTP 200 response can only contain a result code of
// 1000 (i. e. success), there is no need to parse it.
if (response.getStatusCode() == HttpStatusCodes.STATUS_CODE_BAD_REQUEST) {
success = false;
XjcIirdeaResult result = parseResult(content);
logger.atWarning().log(
"PUT rejected, status code %s:\n%s\n%s",
result.getCode().getValue(), result.getMsg(), result.getDescription());
responseCode = connection.getResponseCode();
// Only responses with a 200 or 400 status have a body. For everything else, we can return
// false early.
if (responseCode != STATUS_CODE_OK && responseCode != STATUS_CODE_BAD_REQUEST) {
logger.atWarning().log("Connection to ICANN server failed", connection);
return false;
}
content = UrlConnectionUtils.getResponseBytes(connection);
} finally {
if (response != null) {
response.disconnect();
} else {
success = false;
logger.atWarning().log("Received null response from ICANN server at %s", uploadUrl);
}
connection.disconnect();
}
return success;
// We know that an HTTP 200 response can only contain a result code of
// 1000 (i. e. success), there is no need to parse it.
// See: https://tools.ietf.org/html/draft-lozano-icann-registry-interfaces-13#page-16
if (responseCode != STATUS_CODE_OK) {
XjcIirdeaResult result = parseResult(content);
logger.atWarning().log(
"PUT rejected, status code %s:\n%s\n%s",
result.getCode().getValue(), result.getMsg(), result.getDescription());
return false;
}
return true;
}
private XjcIirdeaResult parseResult(byte[] content) throws XmlException {
private static XjcIirdeaResult parseResult(byte[] content) throws XmlException {
XjcIirdeaResponseElement response =
XjcXmlTransformer.unmarshal(
XjcIirdeaResponseElement.class, new ByteArrayInputStream(content));
@@ -141,7 +125,7 @@ public class IcannHttpReporter {
}
/** Verifies a given report filename matches the pattern tld-reportType-yyyyMM.csv. */
private void validateReportFilename(String filename) {
private static void validateReportFilename(String filename) {
checkArgument(
filename.matches("[a-z0-9.\\-]+-((activity)|(transactions))-[0-9]{6}\\.csv"),
"Expected file format: tld-reportType-yyyyMM.csv, got %s instead",
@@ -149,12 +133,12 @@ public class IcannHttpReporter {
assertTldExists(getTld(filename));
}
private String getTld(String filename) {
private static String getTld(String filename) {
// Extract the TLD, up to second-to-last hyphen in the filename (works with international TLDs)
return filename.substring(0, filename.lastIndexOf('-', filename.lastIndexOf('-') - 1));
}
private String makeUrl(String filename) {
private URL makeUrl(String filename) throws MalformedURLException {
// Filename is in the format tld-reportType-yearMonth.csv
String tld = getTld(filename);
// Remove the tld- prefix and csv suffix
@@ -164,7 +148,7 @@ public class IcannHttpReporter {
// Re-add hyphen between year and month, because ICANN is inconsistent between filename and URL
String yearMonth =
YearMonth.parse(elements.get(1), DateTimeFormat.forPattern("yyyyMM")).toString("yyyy-MM");
return String.format("%s/%s/%s", getUrlPrefix(reportType), tld, yearMonth);
return new URL(String.format("%s/%s/%s", getUrlPrefix(reportType), tld, yearMonth));
}
private String getUrlPrefix(ReportType reportType) {

View File

@@ -14,20 +14,18 @@
package google.registry.request;
import com.google.api.client.extensions.appengine.http.UrlFetchTransport;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.appengine.api.urlfetch.URLFetchService;
import com.google.appengine.api.urlfetch.URLFetchServiceFactory;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import dagger.Module;
import dagger.Provides;
import java.net.HttpURLConnection;
import javax.inject.Singleton;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
/** Dagger modules for App Engine services and other vendor classes. */
public final class Modules {
@@ -37,18 +35,16 @@ public final class Modules {
public static final class UrlConnectionServiceModule {
@Provides
static UrlConnectionService provideUrlConnectionService() {
return url -> (HttpURLConnection) url.openConnection();
}
}
/** Dagger module for {@link URLFetchService}. */
@Module
public static final class UrlFetchServiceModule {
private static final URLFetchService fetchService = URLFetchServiceFactory.getURLFetchService();
@Provides
static URLFetchService provideUrlFetchService() {
return fetchService;
return url -> {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
if (connection instanceof HttpsURLConnection) {
HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
SSLContext tls13Context = SSLContext.getInstance("TLSv1.3");
tls13Context.init(null, null, null);
httpsConnection.setSSLSocketFactory(tls13Context.getSocketFactory());
}
return connection;
};
}
}
@@ -72,17 +68,6 @@ public final class Modules {
}
}
/** Dagger module that causes the App Engine's URL fetcher to be used for Google APIs requests. */
@Module
public static final class UrlFetchTransportModule {
private static final UrlFetchTransport HTTP_TRANSPORT = new UrlFetchTransport();
@Provides
static HttpTransport provideHttpTransport() {
return HTTP_TRANSPORT;
}
}
/**
* Dagger module that provides standard {@link NetHttpTransport}. Used in non App Engine
* environment.

View File

@@ -17,6 +17,7 @@ package google.registry.request;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
@@ -162,6 +163,10 @@ public class RequestHandler<C> {
} catch (HttpException e) {
e.send(rsp);
success = false;
} catch (Exception e) {
rsp.setStatus(SC_INTERNAL_SERVER_ERROR);
rsp.getWriter().write("Internal server error, please try again later");
logger.atSevere().withCause(e).log("Encountered internal server error");
} finally {
requestMetrics.record(
new Duration(startTime, clock.nowUtc()),

View File

@@ -27,15 +27,20 @@ import com.google.common.io.ByteStreams;
import com.google.common.net.MediaType;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLConnection;
import java.util.Random;
/** Utilities for common functionality relating to {@link java.net.URLConnection}s. */
public class UrlConnectionUtils {
/** Utilities for common functionality relating to {@link URLConnection}s. */
public final class UrlConnectionUtils {
private UrlConnectionUtils() {}
/** Retrieves the response from the given connection as a byte array. */
public static byte[] getResponseBytes(URLConnection connection) throws IOException {
return ByteStreams.toByteArray(connection.getInputStream());
try (InputStream is = connection.getInputStream()) {
return ByteStreams.toByteArray(is);
}
}
/** Sets auth on the given connection with the given username/password. */

View File

@@ -15,8 +15,6 @@
package google.registry.request.auth;
import com.google.common.collect.ImmutableList;
import google.registry.flows.EppTlsAction;
import google.registry.flows.TlsCredentials;
import google.registry.request.auth.AuthSettings.AuthLevel;
import google.registry.request.auth.AuthSettings.AuthMethod;
import google.registry.request.auth.AuthSettings.UserPolicy;
@@ -48,30 +46,18 @@ public enum Auth {
* Allows anyone to access, as long as they are logged in.
*
* <p>This is used by legacy registrar console programmatic endpoints (those that extend {@link
* JsonGetAction}, which are accessed via XHR requests sent from a logged-in user when performing
* JsonGetAction}), which are accessed via XHR requests sent from a logged-in user when performing
* actions on the console.
*/
AUTH_PUBLIC_LOGGED_IN(
ImmutableList.of(AuthMethod.API, AuthMethod.LEGACY), AuthLevel.USER, UserPolicy.PUBLIC),
/**
* Allows any client to access, as long as they are logged in via API-based authentication
* mechanisms.
* Allows only the app itself (via service accounts) or admins to access.
*
* <p>This is used by the proxy to access Nomulus endpoints. The proxy service account does NOT
* have admin privileges. For EPP, we handle client authentication within {@link EppTlsAction},
* using {@link TlsCredentials}. For WHOIS, anyone connecting to the proxy can access.
*
* <p>Note that the proxy service account DOES need to be allow-listed in the {@code
* auth.allowedServiceAccountEmails} field in the config YAML file in order for OIDC-based
* authentication to pass.
*/
AUTH_API_PUBLIC(ImmutableList.of(AuthMethod.API), AuthLevel.APP, UserPolicy.PUBLIC),
/**
* Allows only admins to access.
*
* <p>This applies to the majority of the endpoints.
* <p>This applies to the majority of the endpoints. For APP level authentication to work, the
* associated service account needs to be allowlisted in the {@code
* auth.allowedServiceAccountEmails} field in the config YAML file.
*/
AUTH_API_ADMIN(ImmutableList.of(AuthMethod.API), AuthLevel.APP, UserPolicy.ADMIN);

View File

@@ -16,8 +16,6 @@ package google.registry.request.auth;
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
import com.google.appengine.api.oauth.OAuthService;
import com.google.appengine.api.oauth.OAuthServiceFactory;
import com.google.auth.oauth2.TokenVerifier;
import com.google.common.collect.ImmutableList;
import dagger.Module;
@@ -36,9 +34,6 @@ public class AuthModule {
// IAP-signed JWT will be in this header.
// See https://cloud.google.com/iap/docs/signed-headers-howto#securing_iap_headers.
public static final String IAP_HEADER_NAME = "X-Goog-IAP-JWT-Assertion";
// GAE will put the content in header "proxy-authorization" in this header when it routes the
// request to the app.
public static final String PROXY_HEADER_NAME = "X-Google-Proxy-Authorization";
public static final String BEARER_PREFIX = "Bearer ";
// TODO: Change the IAP audience format once we are on GKE.
// See: https://cloud.google.com/iap/docs/signed-headers-howto#verifying_the_jwt_payload
@@ -46,16 +41,12 @@ public class AuthModule {
private static final String IAP_ISSUER_URL = "https://cloud.google.com/iap";
private static final String REGULAR_ISSUER_URL = "https://accounts.google.com";
/** Provides the custom authentication mechanisms (including OAuth and OIDC). */
/** Provides the custom authentication mechanisms. */
@Provides
ImmutableList<AuthenticationMechanism> provideApiAuthenticationMechanisms(
OAuthAuthenticationMechanism oauthAuthenticationMechanism,
IapOidcAuthenticationMechanism iapOidcAuthenticationMechanism,
RegularOidcAuthenticationMechanism regularOidcAuthenticationMechanism) {
return ImmutableList.of(
oauthAuthenticationMechanism,
iapOidcAuthenticationMechanism,
regularOidcAuthenticationMechanism);
return ImmutableList.of(iapOidcAuthenticationMechanism, regularOidcAuthenticationMechanism);
}
@Qualifier
@@ -64,12 +55,6 @@ public class AuthModule {
@Qualifier
@interface RegularOidc {}
/** Provides the OAuthService instance. */
@Provides
OAuthService provideOauthService() {
return OAuthServiceFactory.getOAuthService();
}
@Provides
@IapOidc
@Singleton
@@ -98,11 +83,7 @@ public class AuthModule {
@Singleton
TokenExtractor provideRegularTokenExtractor() {
return request -> {
// TODO: only check the Authorizaiton header after the migration to OIDC is complete.
String rawToken = request.getHeader(PROXY_HEADER_NAME);
if (rawToken == null) {
rawToken = request.getHeader(AUTHORIZATION);
}
String rawToken = request.getHeader(AUTHORIZATION);
if (rawToken != null && rawToken.startsWith(BEARER_PREFIX)) {
return rawToken.substring(BEARER_PREFIX.length());
}

View File

@@ -14,7 +14,9 @@
package google.registry.request.auth;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.request.auth.AuthSettings.AuthLevel.APP;
import static google.registry.request.auth.AuthSettings.AuthLevel.USER;
import com.google.auto.value.AutoValue;
import google.registry.request.auth.AuthSettings.AuthLevel;
@@ -22,8 +24,8 @@ import java.util.Optional;
import javax.annotation.Nullable;
/**
* Results of authentication for a given HTTP request, as emitted by an
* {@link AuthenticationMechanism}.
* Results of authentication for a given HTTP request, as emitted by an {@link
* AuthenticationMechanism}.
*/
@AutoValue
public abstract class AuthResult {
@@ -33,6 +35,10 @@ public abstract class AuthResult {
/** Information about the authenticated user, if there is one. */
public abstract Optional<UserAuthInfo> userAuthInfo();
/** Service account email of the authenticated app, if there is one. */
@SuppressWarnings("unused") // The service account will be logged upon successful login.
public abstract Optional<String> appServiceAccount();
public boolean isAuthenticated() {
return authLevel() != AuthLevel.NONE;
}
@@ -47,15 +53,27 @@ public abstract class AuthResult {
.orElse("<logged-out user>");
}
public static AuthResult create(AuthLevel authLevel) {
return new AutoValue_AuthResult(authLevel, Optional.empty());
public static AuthResult createApp(String email) {
return create(APP, null, email);
}
public static AuthResult create(AuthLevel authLevel, @Nullable UserAuthInfo userAuthInfo) {
if (authLevel == AuthLevel.USER) {
checkNotNull(userAuthInfo);
}
return new AutoValue_AuthResult(authLevel, Optional.ofNullable(userAuthInfo));
public static AuthResult createUser(UserAuthInfo userAuthInfo) {
return create(USER, userAuthInfo, null);
}
private static AuthResult create(
AuthLevel authLevel, @Nullable UserAuthInfo userAuthInfo, @Nullable String email) {
checkArgument(
userAuthInfo == null || email == null,
"User auth info and service account email cannot be specificed at the same time");
checkArgument(
authLevel != USER || userAuthInfo != null,
"User auth info must be specified for auth level USER");
checkArgument(
authLevel != APP || email != null,
"Service account email must be specified for auth level APP");
return new AutoValue_AuthResult(
authLevel, Optional.ofNullable(userAuthInfo), Optional.ofNullable(email));
}
/**
@@ -67,5 +85,5 @@ public abstract class AuthResult {
* returns NOT_AUTHENTICATED in this case, as opposed to absent() if authentication failed and was
* required. So as a return from an authorization check, this can be treated as a success.
*/
public static final AuthResult NOT_AUTHENTICATED = create(AuthLevel.NONE);
public static final AuthResult NOT_AUTHENTICATED = create(AuthLevel.NONE, null, null);
}

View File

@@ -17,6 +17,7 @@ package google.registry.request.auth;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.Immutable;
import google.registry.model.console.UserRoles;
/**
* Parameters used to configure the authenticator.
@@ -42,7 +43,10 @@ public abstract class AuthSettings {
/** Available methods for authentication. */
public enum AuthMethod {
/** Authentication methods suitable for API-style access, such as OAuth 2. */
/**
* Authentication methods suitable for API-style access, such as {@link
* OidcTokenAuthenticationMechanism}.
*/
API,
/** Legacy authentication using cookie-based App Engine Users API. Must come last if present. */
@@ -68,10 +72,11 @@ public abstract class AuthSettings {
/**
* Authentication required, but user not required.
*
* <p>In Auth: Authentication is required, but app-internal authentication (which isn't
* associated with a specific user) is permitted.
* <p>In Auth: authentication is required, but App-internal authentication (which isn't
* associated with a specific user, but a service account) is permitted. Examples include
* requests from Cloud Tasks, Cloud Scheduler, and the proxy.
*
* <p>In AuthResult: App-internal authentication was successful.
* <p>In AuthResult: App-internal authentication (via service accounts) was successful.
*/
APP,
@@ -93,10 +98,14 @@ public abstract class AuthSettings {
PUBLIC,
/**
* If there is a user, it must be an admin, as determined by isUserAdmin().
* If there is a user, it must be an admin, as determined by {@link UserAuthInfo#isUserAdmin()}.
*
* <p>Note that, according to App Engine, anybody with access to the app in the GCP Console,
* <p>Note that, if the user returned is an App Engine {@link
* com.google.appengine.api.users.User} , anybody with access to the app in the GCP Console,
* including editors and viewers, is an admin.
*
* <p>On the other hand, if the user is a {@link google.registry.model.console.User}, the admin
* role is explicitly defined in that object via the {@link UserRoles#isAdmin()} method.
*/
ADMIN
}

View File

@@ -19,7 +19,7 @@ import javax.servlet.http.HttpServletRequest;
/**
* A particular way to authenticate an HTTP request, returning an {@link AuthResult}.
*
* <p>For instance, a request could be authenticated using OAuth, via special request headers, etc.
* <p>For instance, a request could be authenticated using OIDC, via special request headers, etc.
*/
public interface AuthenticationMechanism {

View File

@@ -16,8 +16,7 @@ package google.registry.request.auth;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.base.Strings.nullToEmpty;
import static google.registry.request.auth.AuthSettings.AuthLevel.NONE;
import static google.registry.request.auth.AuthSettings.AuthLevel.USER;
import static google.registry.request.auth.AuthResult.NOT_AUTHENTICATED;
import static google.registry.security.XsrfTokenManager.P_CSRF_TOKEN;
import static google.registry.security.XsrfTokenManager.X_CSRF_TOKEN;
@@ -49,15 +48,14 @@ public class LegacyAuthenticationMechanism implements AuthenticationMechanism {
@Override
public AuthResult authenticate(HttpServletRequest request) {
if (!userService.isUserLoggedIn()) {
return AuthResult.create(NONE);
return NOT_AUTHENTICATED;
}
if (!SAFE_METHODS.contains(request.getMethod()) && !validateXsrf(request)) {
return AuthResult.create(NONE);
return NOT_AUTHENTICATED;
}
return AuthResult.create(
USER,
return AuthResult.createUser(
UserAuthInfo.create(userService.getCurrentUser(), userService.isUserAdmin()));
}

View File

@@ -1,136 +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.request.auth;
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
import static google.registry.request.auth.AuthModule.BEARER_PREFIX;
import static google.registry.request.auth.AuthSettings.AuthLevel.NONE;
import static google.registry.request.auth.AuthSettings.AuthLevel.USER;
import com.google.appengine.api.oauth.OAuthRequestException;
import com.google.appengine.api.oauth.OAuthService;
import com.google.appengine.api.oauth.OAuthServiceFailureException;
import com.google.appengine.api.users.User;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryConfig.Config;
import google.registry.config.RegistryEnvironment;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
/**
* OAuth authentication mechanism, using the OAuthService interface.
*
* <p>Only OAuth version 2 is supported.
*/
public class OAuthAuthenticationMechanism implements AuthenticationMechanism {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final OAuthService oauthService;
/** The available OAuth scopes for which {@link OAuthService} should check. */
private final ImmutableSet<String> availableOauthScopes;
/** The OAuth scopes which must all be present for authentication to succeed. */
private final ImmutableSet<String> requiredOauthScopes;
private final ImmutableSet<String> allowedOauthClientIds;
@Inject
public OAuthAuthenticationMechanism(
OAuthService oauthService,
@Config("availableOauthScopes") ImmutableSet<String> availableOauthScopes,
@Config("requiredOauthScopes") ImmutableSet<String> requiredOauthScopes,
@Config("allowedOauthClientIds") ImmutableSet<String> allowedOauthClientIds) {
this.oauthService = oauthService;
this.availableOauthScopes = availableOauthScopes;
this.requiredOauthScopes = requiredOauthScopes;
this.allowedOauthClientIds = allowedOauthClientIds;
}
@Override
public AuthResult authenticate(HttpServletRequest request) {
// Make sure that there is an Authorization header in Bearer form. OAuthService also accepts
// tokens in the request body and URL string, but we should not use those, since they are more
// likely to be logged than the Authorization header. Checking to make sure there's a token also
// avoids unnecessary RPCs, since OAuthService itself does not check whether the header is
// present. In theory, there could be more than one Authorization header, but we only check the
// first one, because there's not a legitimate use case for having more than one, and
// OAuthService itself only looks at the first one anyway.
String header = request.getHeader(AUTHORIZATION);
if ((header == null) || !header.startsWith(BEARER_PREFIX)) {
if (header != null) {
logger.atInfo().log("Invalid authorization header.");
}
return AuthResult.create(NONE);
}
// Assume that, if a bearer token is found, it's what OAuthService will use to attempt
// authentication. This is not technically guaranteed by the contract of OAuthService; see
// OAuthTokenInfo for more information.
String rawAccessToken =
RegistryEnvironment.get() == RegistryEnvironment.PRODUCTION
? "Raw token redacted in prod"
: header.substring(BEARER_PREFIX.length());
// Get the OAuth information. The various oauthService method calls use a single cached
// authentication result, so we can call them one by one.
User currentUser;
boolean isUserAdmin;
String oauthClientId;
ImmutableSet<String> authorizedScopes;
try {
String[] availableOauthScopeArray = availableOauthScopes.toArray(new String[0]);
currentUser = oauthService.getCurrentUser(availableOauthScopeArray);
isUserAdmin = oauthService.isUserAdmin(availableOauthScopeArray);
logger.atInfo().log(
"Current user: %s (%s).", currentUser, isUserAdmin ? "admin" : "not admin");
oauthClientId = oauthService.getClientId(availableOauthScopeArray);
logger.atInfo().log("OAuth client ID: %s", oauthClientId);
authorizedScopes =
ImmutableSet.copyOf(oauthService.getAuthorizedScopes(availableOauthScopeArray));
logger.atInfo().log("Authorized scope(s): %s", authorizedScopes);
} catch (OAuthRequestException | OAuthServiceFailureException e) {
logger.atInfo().withCause(e).log("Unable to get OAuth information.");
return AuthResult.create(NONE);
}
if ((currentUser == null) || (oauthClientId == null) || (authorizedScopes == null)) {
return AuthResult.create(NONE);
}
// Make sure that the client ID matches, to avoid a confused deputy attack; see:
// http://stackoverflow.com/a/17439317/1179226
if (!allowedOauthClientIds.contains(oauthClientId)) {
logger.atInfo().log("OAuth client ID is not allowed.");
return AuthResult.create(NONE);
}
// Make sure that all required scopes are present.
if (!authorizedScopes.containsAll(requiredOauthScopes)) {
logger.atInfo().log("Missing required scope(s).");
return AuthResult.create(NONE);
}
// Create the {@link AuthResult}, including the OAuth token info.
return AuthResult.create(
USER,
UserAuthInfo.create(
currentUser,
isUserAdmin,
OAuthTokenInfo.create(
ImmutableSet.copyOf(authorizedScopes), oauthClientId, rawAccessToken)));
}
}

View File

@@ -1,48 +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.request.auth;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableSet;
/** Information provided by the OAuth authentication mechanism (only) about the session. */
@AutoValue
public abstract class OAuthTokenInfo {
/** Authorized OAuth scopes granted by the access token provided with the request. */
abstract ImmutableSet<String> authorizedScopes();
/** OAuth client ID from the access token provided with the request. */
abstract String oauthClientId();
/**
* Raw OAuth access token value provided with the request, for passing along to downstream APIs as
* appropriate.
*
* <p>Note that the request parsing code makes certain assumptions about whether the Authorization
* header was used as the source of the token. Because OAuthService could theoretically fall back
* to some other source of authentication, it might be possible for rawAccessToken not to have
* been the source of OAuth authentication. Looking at the code of OAuthService, that could not
* currently happen, but if OAuthService were modified in the future so that it tried the bearer
* token, and then when that failed, fell back to another, successful authentication path, then
* rawAccessToken might not be valid.
*/
abstract String rawAccessToken();
static OAuthTokenInfo create(
ImmutableSet<String> authorizedScopes, String oauthClientId, String rawAccessToken) {
return new AutoValue_OAuthTokenInfo(authorizedScopes, oauthClientId, rawAccessToken);
}
}

View File

@@ -14,8 +14,6 @@
package google.registry.request.auth;
import static google.registry.request.auth.AuthSettings.AuthLevel.APP;
import com.google.api.client.json.webtoken.JsonWebSignature;
import com.google.auth.oauth2.TokenVerifier;
import com.google.common.annotations.VisibleForTesting;
@@ -97,12 +95,11 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
}
Optional<User> maybeUser = UserDao.loadUser(email);
if (maybeUser.isPresent()) {
return AuthResult.create(AuthLevel.USER, UserAuthInfo.create(maybeUser.get()));
return AuthResult.createUser(UserAuthInfo.create(maybeUser.get()));
}
// TODO: implement caching so we don't have to look up the database for every request.
logger.atInfo().log("No end user found for email address %s", email);
if (serviceAccountEmails.stream().anyMatch(e -> e.equals(email))) {
return AuthResult.create(APP);
return AuthResult.createApp(email);
}
logger.atInfo().log("No service account found for email address %s", email);
logger.atWarning().log(
@@ -153,15 +150,8 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
*
* <p>If the endpoint is not behind IAP, we can try to authenticate the OIDC token supplied in the
* request header directly. Ideally we would like all endpoints to be behind IAP, but being able
* to authenticate the token directly provides us with the flexibility to do away with OAuth-based
* {@link OAuthAuthenticationMechanism} that is tied to App Engine runtime without having to turn
* on IAP, which is an all-or-nothing switch for each GAE service (i.e. no way to turn it on only
* for certain GAE endpoints).
*
* <p>Note that this mechanism will try to first extract the token under the "proxy-authorization"
* header, before trying "authorization". This is because currently the GAE OAuth service always
* uses "authorization", and we would like to provide a way for both auth mechanisms to be working
* at the same time for the same request.
* to authenticate the token directly provides us with some extra flexibility that comes in handy,
* at least during the migration to GKE.
*
* @see <a href=https://datatracker.ietf.org/doc/html/rfc6750>Bearer Token Usage</a>
*/

View File

@@ -60,7 +60,8 @@ public class RequestAuthenticator {
if (auth.minimumLevel() == APP && !authResult.isAuthenticated()) {
logger.atWarning().log("Not authorized; no authentication found.");
return Optional.empty();
} else if (auth.minimumLevel() == USER && authResult.authLevel() != USER) {
}
if (auth.minimumLevel() == USER && authResult.authLevel() != USER) {
logger.atWarning().log("Not authorized; no authenticated user.");
return Optional.empty();
}
@@ -81,12 +82,12 @@ public class RequestAuthenticator {
* @param req the {@link HttpServletRequest}; some authentication mechanisms use HTTP headers
* @return an authentication result; if no authentication was made, returns NOT_AUTHENTICATED
*/
private AuthResult authenticate(AuthSettings auth, HttpServletRequest req) {
AuthResult authenticate(AuthSettings auth, HttpServletRequest req) {
checkAuthConfig(auth);
for (AuthMethod authMethod : auth.methods()) {
AuthResult authResult;
switch (authMethod) {
// API-based user authentication mechanisms, such as OAuth and OIDC.
// API-based user authentication mechanisms, such as OIDC.
case API:
for (AuthenticationMechanism authMechanism : apiAuthenticationMechanisms) {
authResult = authMechanism.authenticate(req);
@@ -113,10 +114,9 @@ public class RequestAuthenticator {
/** Validates an AuthSettings object, checking for invalid setting combinations. */
static void checkAuthConfig(AuthSettings auth) {
ImmutableList<AuthMethod> authMethods = ImmutableList.copyOf(auth.methods());
checkArgument(!authMethods.isEmpty(), "Must specify at least one auth method");
checkArgument(!auth.methods().isEmpty(), "Must specify at least one auth method");
checkArgument(
Ordering.explicit(AuthMethod.API, AuthMethod.LEGACY).isStrictlyOrdered(authMethods),
Ordering.explicit(AuthMethod.API, AuthMethod.LEGACY).isStrictlyOrdered(auth.methods()),
"Auth methods must be unique and strictly in order - API, LEGACY");
checkArgument(
(auth.minimumLevel() != NONE) || (auth.userPolicy() != ADMIN),

View File

@@ -22,6 +22,8 @@ import java.util.Optional;
@AutoValue
public abstract class UserAuthInfo {
public abstract Optional<google.registry.model.console.User> consoleUser();
/** User object from the AppEngine Users API. */
public abstract Optional<User> appEngineUser();
@@ -34,11 +36,6 @@ public abstract class UserAuthInfo {
*/
public abstract boolean isUserAdmin();
public abstract Optional<google.registry.model.console.User> consoleUser();
/** Used by the OAuth authentication mechanism (only) to return information about the session. */
public abstract Optional<OAuthTokenInfo> oauthTokenInfo();
public String getEmailAddress() {
return appEngineUser()
.map(User::getEmail)
@@ -51,20 +48,12 @@ public abstract class UserAuthInfo {
.orElseGet(() -> consoleUser().get().getEmailAddress());
}
public static UserAuthInfo create(
User user, boolean isUserAdmin) {
return new AutoValue_UserAuthInfo(
Optional.of(user), isUserAdmin, Optional.empty(), Optional.empty());
}
public static UserAuthInfo create(
User user, boolean isUserAdmin, OAuthTokenInfo oauthTokenInfo) {
return new AutoValue_UserAuthInfo(
Optional.of(user), isUserAdmin, Optional.empty(), Optional.of(oauthTokenInfo));
public static UserAuthInfo create(User user, boolean isUserAdmin) {
return new AutoValue_UserAuthInfo(Optional.empty(), Optional.of(user), isUserAdmin);
}
public static UserAuthInfo create(google.registry.model.console.User user) {
return new AutoValue_UserAuthInfo(
Optional.empty(), user.getUserRoles().isAdmin(), Optional.of(user), Optional.empty());
Optional.of(user), Optional.empty(), user.getUserRoles().isAdmin());
}
}

View File

@@ -71,6 +71,11 @@ public class ConfigureTldCommand extends MutatingCommand {
+ " configuration in the database.")
boolean breakglass;
@Parameter(
names = {"-d", "--dryrun"},
description = "Does not execute the entity mutation")
boolean dryrun;
@Inject ObjectMapper mapper;
@Inject
@@ -126,6 +131,9 @@ public class ConfigureTldCommand extends MutatingCommand {
@Override
protected boolean dontRunCommand() {
if (dryrun) {
return true;
}
if (!newDiff) {
if (oldTldInBreakglass && !breakglass) {
// Run command to remove breakglass mode

View File

@@ -50,13 +50,6 @@ final class RegistryCli implements CommandRunner {
description = "Sets the default environment to run the command.")
private RegistryToolEnvironment environment = RegistryToolEnvironment.PRODUCTION;
@Parameter(
names = "--oauth",
description =
"Turn on OAuth-based authentication, the usage of which is to be deprecated. Use"
+ " `create_user` to create an Admin user that allows for OIDC-based authentication.")
private boolean oAuth = false;
@Parameter(
names = {"-c", "--commands"},
description = "Returns all command names.")
@@ -168,7 +161,6 @@ final class RegistryCli implements CommandRunner {
DaggerRegistryToolComponent.builder()
.credentialFilePath(credentialJson)
.sqlAccessInfoFile(sqlAccessInfoFile)
.addOAuthHeader(oAuth)
.build();
// JCommander stores sub-commands as nested JCommander objects containing a list of user objects

View File

@@ -39,7 +39,6 @@ import google.registry.privileges.secretmanager.SecretManagerModule;
import google.registry.rde.RdeModule;
import google.registry.request.Modules.GsonModule;
import google.registry.request.Modules.UrlConnectionServiceModule;
import google.registry.request.Modules.UrlFetchServiceModule;
import google.registry.request.Modules.UserServiceModule;
import google.registry.tools.AuthModule.LocalCredentialModule;
import google.registry.util.UtilsModule;
@@ -77,7 +76,6 @@ import javax.inject.Singleton;
SecretManagerKeyringModule.class,
SecretManagerModule.class,
UrlConnectionServiceModule.class,
UrlFetchServiceModule.class,
UserServiceModule.class,
UtilsModule.class,
VoidDnsWriterModule.class,
@@ -191,9 +189,6 @@ interface RegistryToolComponent {
@BindsInstance
Builder sqlAccessInfoFile(@Nullable @Config("sqlAccessInfoFile") String sqlAccessInfoFile);
@BindsInstance
Builder addOAuthHeader(@Config("addOauthHeader") boolean addOauthHeader);
RegistryToolComponent build();
}
}

View File

@@ -14,7 +14,7 @@
package google.registry.tools;
import static com.google.common.net.HttpHeaders.PROXY_AUTHORIZATION;
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.javanet.NetHttpTransport;
@@ -42,8 +42,7 @@ final class RequestFactoryModule {
@Provides
static HttpRequestFactory provideHttpRequestFactory(
@ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle,
@Config("oauthClientId") String oauthClientId,
@Config("addOauthHeader") boolean addOauthHeader) {
@Config("oauthClientId") String oauthClientId) {
if (RegistryConfig.areServersLocal()) {
return new NetHttpTransport()
.createRequestFactory(
@@ -55,15 +54,11 @@ final class RequestFactoryModule {
return new NetHttpTransport()
.createRequestFactory(
request -> {
if (addOauthHeader) {
// Use the standard credential initializer to set the Authorization header
credentialsBundle.getHttpRequestInitializer().initialize(request);
}
// Set OIDC token as the alternative bearer token.
// Set OIDC token as the bearer token.
request
.getHeaders()
.set(
PROXY_AUTHORIZATION,
AUTHORIZATION,
"Bearer "
+ OidcTokenUtils.createOidcToken(credentialsBundle, oauthClientId));
// GAE request times out after 10 min, so here we set the timeout to 10 min. This is

View File

@@ -19,6 +19,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.Iterables.getLast;
import static google.registry.dns.DnsUtils.requestDomainDnsRefresh;
import static google.registry.model.tld.Tlds.assertTldsExist;
import static google.registry.persistence.PersistenceModule.TransactionIsolationLevel.TRANSACTION_REPEATABLE_READ;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.RequestParameters.PARAM_TLDS;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
@@ -82,13 +83,16 @@ public class RefreshDnsForAllDomainsAction implements Runnable {
public void run() {
assertTldsExist(tlds);
checkArgument(batchSize > 0, "Must specify a positive number for batch size");
int smearMinutes = tm().transact(this::calculateSmearMinutes);
int smearMinutes = tm().transact(this::calculateSmearMinutes, TRANSACTION_REPEATABLE_READ);
ImmutableList<String> domainsBatch;
@Nullable String lastInPreviousBatch = null;
do {
Optional<String> lastInPreviousBatchOpt = Optional.ofNullable(lastInPreviousBatch);
domainsBatch = tm().transact(() -> refreshBatch(lastInPreviousBatchOpt, smearMinutes));
domainsBatch =
tm().transact(
() -> refreshBatch(lastInPreviousBatchOpt, smearMinutes),
TRANSACTION_REPEATABLE_READ);
lastInPreviousBatch = domainsBatch.isEmpty() ? null : getLast(domainsBatch);
} while (domainsBatch.size() == batchSize);
}

View File

@@ -40,15 +40,24 @@ public class ConsoleUserDataAction implements JsonGetAction {
private final AuthResult authResult;
private final Response response;
private final String productName;
private final String supportPhoneNumber;
private final String supportEmail;
private final String technicalDocsUrl;
@Inject
public ConsoleUserDataAction(
AuthResult authResult,
Response response,
@Config("productName") String productName,
@Config("supportEmail") String supportEmail,
@Config("supportPhoneNumber") String supportPhoneNumber,
@Config("technicalDocsUrl") String technicalDocsUrl) {
this.response = response;
this.authResult = authResult;
this.productName = productName;
this.supportEmail = supportEmail;
this.supportPhoneNumber = supportPhoneNumber;
this.technicalDocsUrl = technicalDocsUrl;
}
@@ -74,6 +83,10 @@ public class ConsoleUserDataAction implements JsonGetAction {
// auth checks.
"isAdmin", user.getUserRoles().isAdmin(),
"globalRole", user.getUserRoles().getGlobalRole(),
// Include static contact resources in this call to minimize round trips
"productName", productName,
"supportEmail", supportEmail,
"supportPhoneNumber", supportPhoneNumber,
// Is used by UI to construct a link to registry resources
"technicalDocsUrl", technicalDocsUrl));

View File

@@ -17,7 +17,6 @@ package google.registry.ui.server.console.settings;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.POST;
import avro.shaded.com.google.common.collect.ImmutableList;
import com.google.api.client.http.HttpStatusCodes;
import com.google.gson.Gson;
import google.registry.flows.certs.CertificateChecker;
@@ -103,42 +102,31 @@ public class SecurityAction implements JsonGetAction {
.asBuilder()
.setIpAddressAllowList(registrarParameter.getIpAddressAllowList());
boolean hasInvalidCerts =
ImmutableList.of(
registrarParameter.getClientCertificate(),
registrarParameter.getFailoverClientCertificate())
.stream()
.filter(Optional::isPresent)
.map(Optional::get)
.anyMatch(
cert -> {
try {
certificateChecker.validateCertificate(cert);
return false;
} catch (InsecureCertificateException e) {
return true;
}
});
if (hasInvalidCerts) {
try {
if (!savedRegistrar
.getClientCertificate()
.equals(registrarParameter.getClientCertificate())) {
if (registrarParameter.getClientCertificate().isPresent()) {
String newClientCert = registrarParameter.getClientCertificate().get();
certificateChecker.validateCertificate(newClientCert);
updatedRegistrar.setClientCertificate(newClientCert, tm().getTransactionTime());
}
}
if (!savedRegistrar
.getFailoverClientCertificate()
.equals(registrarParameter.getFailoverClientCertificate())) {
if (registrarParameter.getFailoverClientCertificate().isPresent()) {
String newFailoverCert = registrarParameter.getFailoverClientCertificate().get();
certificateChecker.validateCertificate(newFailoverCert);
updatedRegistrar.setFailoverClientCertificate(newFailoverCert, tm().getTransactionTime());
}
}
} catch (InsecureCertificateException e) {
response.setStatus(HttpStatusCodes.STATUS_CODE_BAD_REQUEST);
response.setPayload("Insecure Certificate in parameter");
response.setPayload("Invalid certificate in parameter");
return;
}
registrarParameter
.getClientCertificate()
.ifPresent(
newClientCert ->
updatedRegistrar.setClientCertificate(newClientCert, tm().getTransactionTime()));
registrarParameter
.getFailoverClientCertificate()
.ifPresent(
failoverCert ->
updatedRegistrar.setFailoverClientCertificate(
failoverCert, tm().getTransactionTime()));
tm().put(updatedRegistrar.build());
response.setStatus(HttpStatusCodes.STATUS_CODE_OK);
}

View File

@@ -51,7 +51,7 @@ import org.joda.time.DateTime;
service = Action.Service.PUBAPI,
path = "/_dr/whois",
method = POST,
auth = Auth.AUTH_API_PUBLIC)
auth = Auth.AUTH_API_ADMIN)
public class WhoisAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();

View File

@@ -28,7 +28,6 @@ import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.request.Actions;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthSettings.AuthLevel;
import google.registry.request.auth.UserAuthInfo;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
@@ -48,13 +47,11 @@ abstract class RdapActionBaseTestCase<A extends RdapActionBase> {
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
protected static final AuthResult AUTH_RESULT =
AuthResult.create(
AuthLevel.USER,
AuthResult.createUser(
UserAuthInfo.create(new User("rdap.user@user.com", "gmail.com", "12345"), false));
protected static final AuthResult AUTH_RESULT_ADMIN =
AuthResult.create(
AuthLevel.USER,
AuthResult.createUser(
UserAuthInfo.create(new User("rdap.admin@google.com", "gmail.com", "12345"), true));
protected FakeResponse response = new FakeResponse();

View File

@@ -18,20 +18,27 @@ import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.persistSimpleResource;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.HttpStatusCodes;
import com.google.api.client.http.LowLevelHttpRequest;
import com.google.api.client.testing.http.MockHttpTransport;
import com.google.api.client.testing.http.MockLowLevelHttpRequest;
import com.google.api.client.testing.http.MockLowLevelHttpResponse;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarAddress;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.request.HttpException.InternalServerErrorException;
import google.registry.testing.FakeUrlConnectionService;
import google.registry.util.UrlConnectionException;
import java.io.ByteArrayInputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@@ -61,44 +68,26 @@ public final class UpdateRegistrarRdapBaseUrlsActionTest {
public JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
private static class TestHttpTransport extends MockHttpTransport {
private MockLowLevelHttpRequest requestSent;
private MockLowLevelHttpResponse response;
void setResponse(MockLowLevelHttpResponse response) {
this.response = response;
}
MockLowLevelHttpRequest getRequestSent() {
return requestSent;
}
@Override
public LowLevelHttpRequest buildRequest(String method, String url) {
assertThat(method).isEqualTo("GET");
MockLowLevelHttpRequest httpRequest = new MockLowLevelHttpRequest(url);
httpRequest.setResponse(response);
requestSent = httpRequest;
return httpRequest;
}
}
private TestHttpTransport httpTransport;
private final HttpURLConnection connection = mock(HttpURLConnection.class);
private final FakeUrlConnectionService urlConnectionService =
new FakeUrlConnectionService(connection);
private UpdateRegistrarRdapBaseUrlsAction action;
@BeforeEach
void beforeEach() {
void beforeEach() throws Exception {
action = new UpdateRegistrarRdapBaseUrlsAction();
httpTransport = new TestHttpTransport();
action.httpTransport = httpTransport;
setValidResponse();
action.urlConnectionService = urlConnectionService;
when(connection.getResponseCode()).thenReturn(SC_OK);
when(connection.getInputStream())
.thenReturn(new ByteArrayInputStream(CSV_REPLY.getBytes(StandardCharsets.UTF_8)));
createTld("tld");
}
private void assertCorrectRequestSent() {
assertThat(httpTransport.getRequestSent().getUrl())
.isEqualTo("https://www.iana.org/assignments/registrar-ids/registrar-ids-1.csv");
assertThat(httpTransport.getRequestSent().getHeaders().get("accept-encoding")).isNull();
private void assertCorrectRequestSent() throws Exception {
assertThat(urlConnectionService.getConnectedUrls())
.containsExactly(
new URL("https://www.iana.org/assignments/registrar-ids/registrar-ids-1.csv"));
verify(connection).setRequestProperty("Accept-Encoding", "gzip");
}
private static void persistRegistrar(
@@ -119,14 +108,8 @@ public final class UpdateRegistrarRdapBaseUrlsActionTest {
.build());
}
private void setValidResponse() {
MockLowLevelHttpResponse csvResponse = new MockLowLevelHttpResponse();
csvResponse.setContent(CSV_REPLY);
httpTransport.setResponse(csvResponse);
}
@Test
void testUnknownIana_cleared() {
void testUnknownIana_cleared() throws Exception {
// The IANA ID isn't in the CSV reply
persistRegistrar("someRegistrar", 4123L, Registrar.Type.REAL, "http://rdap.example/blah");
action.run();
@@ -135,7 +118,7 @@ public final class UpdateRegistrarRdapBaseUrlsActionTest {
}
@Test
void testKnownIana_changed() {
void testKnownIana_changed() throws Exception {
// The IANA ID is in the CSV reply
persistRegistrar("someRegistrar", 1448L, Registrar.Type.REAL, "http://rdap.example/blah");
action.run();
@@ -145,7 +128,7 @@ public final class UpdateRegistrarRdapBaseUrlsActionTest {
}
@Test
void testKnownIana_notReal_noChange() {
void testKnownIana_notReal_noChange() throws Exception {
// The IANA ID is in the CSV reply
persistRegistrar("someRegistrar", 9999L, Registrar.Type.INTERNAL, "http://rdap.example/blah");
// Real registrars should actually change
@@ -159,7 +142,7 @@ public final class UpdateRegistrarRdapBaseUrlsActionTest {
}
@Test
void testKnownIana_notReal_nullIANA_noChange() {
void testKnownIana_notReal_nullIANA_noChange() throws Exception {
persistRegistrar("someRegistrar", null, Registrar.Type.TEST, "http://rdap.example/blah");
action.run();
assertCorrectRequestSent();
@@ -168,29 +151,30 @@ public final class UpdateRegistrarRdapBaseUrlsActionTest {
}
@Test
void testFailure_serverErrorResponse() {
MockLowLevelHttpResponse badResponse = new MockLowLevelHttpResponse();
badResponse.setZeroContent();
badResponse.setStatusCode(HttpStatusCodes.STATUS_CODE_SERVER_ERROR);
httpTransport.setResponse(badResponse);
RuntimeException thrown = assertThrows(RuntimeException.class, action::run);
void testFailure_serverErrorResponse() throws Exception {
when(connection.getResponseCode()).thenReturn(SC_INTERNAL_SERVER_ERROR);
when(connection.getInputStream())
.thenReturn(new ByteArrayInputStream("".getBytes(StandardCharsets.UTF_8)));
InternalServerErrorException thrown =
assertThrows(InternalServerErrorException.class, action::run);
verify(connection, times(0)).getInputStream();
assertThat(thrown).hasMessageThat().isEqualTo("Error when retrieving RDAP base URL CSV file");
Throwable cause = thrown.getCause();
assertThat(cause).isInstanceOf(HttpResponseException.class);
assertThat(cause).isInstanceOf(UrlConnectionException.class);
assertThat(cause)
.hasMessageThat()
.isEqualTo("500\nGET https://www.iana.org/assignments/registrar-ids/registrar-ids-1.csv");
.contains("https://www.iana.org/assignments/registrar-ids/registrar-ids-1.csv");
}
@Test
void testFailure_invalidCsv() {
MockLowLevelHttpResponse csvResponse = new MockLowLevelHttpResponse();
csvResponse.setContent("foo,bar\nbaz,foo");
httpTransport.setResponse(csvResponse);
void testFailure_invalidCsv() throws Exception {
when(connection.getInputStream())
.thenReturn(new ByteArrayInputStream("foo,bar\nbaz,foo".getBytes(StandardCharsets.UTF_8)));
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, action::run);
InternalServerErrorException thrown =
assertThrows(InternalServerErrorException.class, action::run);
assertThat(thrown)
.hasCauseThat()
.hasMessageThat()
.isEqualTo("Mapping for ID not found, expected one of [foo, bar]");
}

View File

@@ -14,7 +14,9 @@
package google.registry.rde;
import static com.google.appengine.api.urlfetch.HTTPMethod.PUT;
import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_BAD_REQUEST;
import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_OK;
import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_UNAUTHORIZED;
import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.common.Cursor.CursorType.RDE_REPORT;
@@ -24,25 +26,17 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadByKey;
import static google.registry.testing.DatabaseHelper.persistResource;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static org.joda.time.Duration.standardDays;
import static org.joda.time.Duration.standardSeconds;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import com.google.appengine.api.urlfetch.HTTPHeader;
import com.google.appengine.api.urlfetch.HTTPRequest;
import com.google.appengine.api.urlfetch.HTTPResponse;
import com.google.appengine.api.urlfetch.URLFetchService;
import com.google.cloud.storage.BlobId;
import com.google.cloud.storage.contrib.nio.testing.LocalStorageHelper;
import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.ByteSource;
import google.registry.gcs.GcsUtils;
import google.registry.model.common.Cursor;
@@ -53,25 +47,23 @@ import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationT
import google.registry.request.HttpException.InternalServerErrorException;
import google.registry.request.HttpException.NoContentException;
import google.registry.testing.BouncyCastleProviderExtension;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeKeyringModule;
import google.registry.testing.FakeResponse;
import google.registry.testing.FakeSleeper;
import google.registry.util.Retrier;
import google.registry.testing.FakeUrlConnectionService;
import google.registry.util.UrlConnectionException;
import google.registry.xjc.XjcXmlTransformer;
import google.registry.xjc.rdereport.XjcRdeReportReport;
import google.registry.xml.XmlException;
import java.io.ByteArrayInputStream;
import java.net.SocketTimeoutException;
import java.io.ByteArrayOutputStream;
import java.net.HttpURLConnection;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Optional;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.ArgumentCaptor;
/** Unit tests for {@link RdeReportAction}. */
public class RdeReportActionTest {
@@ -89,9 +81,11 @@ public class RdeReportActionTest {
private final FakeResponse response = new FakeResponse();
private final EscrowTaskRunner runner = mock(EscrowTaskRunner.class);
private final URLFetchService urlFetchService = mock(URLFetchService.class);
private final ArgumentCaptor<HTTPRequest> request = ArgumentCaptor.forClass(HTTPRequest.class);
private final HTTPResponse httpResponse = mock(HTTPResponse.class);
private final HttpURLConnection httpUrlConnection = mock(HttpURLConnection.class);
private final FakeUrlConnectionService urlConnectionService =
new FakeUrlConnectionService(httpUrlConnection);
private final ByteArrayOutputStream connectionOutputStream = new ByteArrayOutputStream();
private final PGPPublicKey encryptKey =
new FakeKeyringModule().get().getRdeStagingEncryptionKey();
private final GcsUtils gcsUtils = new GcsUtils(LocalStorageHelper.getOptions());
@@ -102,9 +96,8 @@ public class RdeReportActionTest {
private RdeReportAction createAction() {
RdeReporter reporter = new RdeReporter();
reporter.reportUrlPrefix = "https://rde-report.example";
reporter.urlFetchService = urlFetchService;
reporter.urlConnectionService = urlConnectionService;
reporter.password = "foo";
reporter.retrier = new Retrier(new FakeSleeper(new FakeClock()), 3);
RdeReportAction action = new RdeReportAction();
action.gcsUtils = gcsUtils;
action.response = response;
@@ -126,6 +119,9 @@ public class RdeReportActionTest {
persistResource(Cursor.createScoped(RDE_UPLOAD, DateTime.parse("2006-06-07TZ"), registry));
gcsUtils.createFromBytes(reportFile, Ghostryde.encode(REPORT_XML.read(), encryptKey));
tm().transact(() -> RdeRevision.saveRevision("test", DateTime.parse("2006-06-06TZ"), FULL, 0));
when(httpUrlConnection.getOutputStream()).thenReturn(connectionOutputStream);
when(httpUrlConnection.getResponseCode()).thenReturn(STATUS_CODE_OK);
when(httpUrlConnection.getInputStream()).thenReturn(IIRDEA_GOOD_XML.openBufferedStream());
}
@Test
@@ -142,24 +138,20 @@ public class RdeReportActionTest {
@Test
void testRunWithLock() throws Exception {
when(httpResponse.getResponseCode()).thenReturn(SC_OK);
when(httpResponse.getContent()).thenReturn(IIRDEA_GOOD_XML.read());
when(urlFetchService.fetch(request.capture())).thenReturn(httpResponse);
createAction().runWithLock(loadRdeReportCursor());
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getContentType()).isEqualTo(PLAIN_TEXT_UTF_8);
assertThat(response.getPayload()).isEqualTo("OK test 2006-06-06T00:00:00.000Z\n");
// Verify the HTTP request was correct.
assertThat(request.getValue().getMethod()).isSameInstanceAs(PUT);
assertThat(request.getValue().getURL().getProtocol()).isEqualTo("https");
assertThat(request.getValue().getURL().getPath()).endsWith("/test/20101017001");
Map<String, String> headers = mapifyHeaders(request.getValue().getHeaders());
assertThat(headers).containsEntry("CONTENT_TYPE", "text/xml");
assertThat(headers).containsEntry("AUTHORIZATION", "Basic dGVzdF9yeTpmb28=");
verify(httpUrlConnection).setRequestMethod("PUT");
assertThat(httpUrlConnection.getURL().getProtocol()).isEqualTo("https");
assertThat(httpUrlConnection.getURL().getPath()).endsWith("/test/20101017001");
verify(httpUrlConnection).setRequestProperty("Content-Type", "text/xml; charset=utf-8");
verify(httpUrlConnection).setRequestProperty("Authorization", "Basic dGVzdF9yeTpmb28=");
// Verify the payload XML was the same as what's in testdata/report.xml.
XjcRdeReportReport report = parseReport(request.getValue().getPayload());
XjcRdeReportReport report = parseReport(connectionOutputStream.toByteArray());
assertThat(report.getId()).isEqualTo("20101017001");
assertThat(report.getCrDate()).isEqualTo(DateTime.parse("2010-10-17T00:15:00.0Z"));
assertThat(report.getWatermark()).isEqualTo(DateTime.parse("2010-10-17T00:00:00Z"));
@@ -167,9 +159,6 @@ public class RdeReportActionTest {
@Test
void testRunWithLock_withPrefix() throws Exception {
when(httpResponse.getResponseCode()).thenReturn(SC_OK);
when(httpResponse.getContent()).thenReturn(IIRDEA_GOOD_XML.read());
when(urlFetchService.fetch(request.capture())).thenReturn(httpResponse);
RdeReportAction action = createAction();
action.runWithLock(loadRdeReportCursor());
assertThat(response.getStatus()).isEqualTo(200);
@@ -177,15 +166,14 @@ public class RdeReportActionTest {
assertThat(response.getPayload()).isEqualTo("OK test 2006-06-06T00:00:00.000Z\n");
// Verify the HTTP request was correct.
assertThat(request.getValue().getMethod()).isSameInstanceAs(PUT);
assertThat(request.getValue().getURL().getProtocol()).isEqualTo("https");
assertThat(request.getValue().getURL().getPath()).endsWith("/test/20101017001");
Map<String, String> headers = mapifyHeaders(request.getValue().getHeaders());
assertThat(headers).containsEntry("CONTENT_TYPE", "text/xml");
assertThat(headers).containsEntry("AUTHORIZATION", "Basic dGVzdF9yeTpmb28=");
verify(httpUrlConnection).setRequestMethod("PUT");
assertThat(httpUrlConnection.getURL().getProtocol()).isEqualTo("https");
assertThat(httpUrlConnection.getURL().getPath()).endsWith("/test/20101017001");
verify(httpUrlConnection).setRequestProperty("Content-Type", "text/xml; charset=utf-8");
verify(httpUrlConnection).setRequestProperty("Authorization", "Basic dGVzdF9yeTpmb28=");
// Verify the payload XML was the same as what's in testdata/report.xml.
XjcRdeReportReport report = parseReport(request.getValue().getPayload());
XjcRdeReportReport report = parseReport(connectionOutputStream.toByteArray());
assertThat(report.getId()).isEqualTo("20101017001");
assertThat(report.getCrDate()).isEqualTo(DateTime.parse("2010-10-17T00:15:00.0Z"));
assertThat(report.getWatermark()).isEqualTo(DateTime.parse("2010-10-17T00:00:00Z"));
@@ -200,9 +188,6 @@ public class RdeReportActionTest {
@Test
void testRunWithLock_withoutPrefix() throws Exception {
when(httpResponse.getResponseCode()).thenReturn(SC_OK);
when(httpResponse.getContent()).thenReturn(IIRDEA_GOOD_XML.read());
when(urlFetchService.fetch(request.capture())).thenReturn(httpResponse);
RdeReportAction action = createAction();
action.prefix = Optional.empty();
gcsUtils.delete(reportFile);
@@ -225,15 +210,14 @@ public class RdeReportActionTest {
assertThat(response.getPayload()).isEqualTo("OK test 2006-06-06T00:00:00.000Z\n");
// Verify the HTTP request was correct.
assertThat(request.getValue().getMethod()).isSameInstanceAs(PUT);
assertThat(request.getValue().getURL().getProtocol()).isEqualTo("https");
assertThat(request.getValue().getURL().getPath()).endsWith("/test/20101017001");
Map<String, String> headers = mapifyHeaders(request.getValue().getHeaders());
assertThat(headers).containsEntry("CONTENT_TYPE", "text/xml");
assertThat(headers).containsEntry("AUTHORIZATION", "Basic dGVzdF9yeTpmb28=");
verify(httpUrlConnection).setRequestMethod("PUT");
assertThat(httpUrlConnection.getURL().getProtocol()).isEqualTo("https");
assertThat(httpUrlConnection.getURL().getPath()).endsWith("/test/20101017001");
verify(httpUrlConnection).setRequestProperty("Content-Type", "text/xml; charset=utf-8");
verify(httpUrlConnection).setRequestProperty("Authorization", "Basic dGVzdF9yeTpmb28=");
// Verify the payload XML was the same as what's in testdata/report.xml.
XjcRdeReportReport report = parseReport(request.getValue().getPayload());
XjcRdeReportReport report = parseReport(connectionOutputStream.toByteArray());
assertThat(report.getId()).isEqualTo("20101017001");
assertThat(report.getCrDate()).isEqualTo(DateTime.parse("2010-10-17T00:15:00.0Z"));
assertThat(report.getWatermark()).isEqualTo(DateTime.parse("2010-10-17T00:00:00Z"));
@@ -246,9 +230,6 @@ public class RdeReportActionTest {
PGPPublicKey encryptKey = new FakeKeyringModule().get().getRdeStagingEncryptionKey();
gcsUtils.createFromBytes(newReport, Ghostryde.encode(REPORT_XML.read(), encryptKey));
tm().transact(() -> RdeRevision.saveRevision("test", DateTime.parse("2006-06-06TZ"), FULL, 1));
when(httpResponse.getResponseCode()).thenReturn(SC_OK);
when(httpResponse.getContent()).thenReturn(IIRDEA_GOOD_XML.read());
when(urlFetchService.fetch(request.capture())).thenReturn(httpResponse);
createAction().runWithLock(loadRdeReportCursor());
assertThat(response.getStatus()).isEqualTo(200);
}
@@ -281,9 +262,8 @@ public class RdeReportActionTest {
@Test
void testRunWithLock_badRequest_throws500WithErrorInfo() throws Exception {
when(httpResponse.getResponseCode()).thenReturn(SC_BAD_REQUEST);
when(httpResponse.getContent()).thenReturn(IIRDEA_BAD_XML.read());
when(urlFetchService.fetch(request.capture())).thenReturn(httpResponse);
when(httpUrlConnection.getResponseCode()).thenReturn(STATUS_CODE_BAD_REQUEST);
when(httpUrlConnection.getInputStream()).thenReturn(IIRDEA_BAD_XML.openBufferedStream());
InternalServerErrorException thrown =
assertThrows(
InternalServerErrorException.class,
@@ -292,38 +272,19 @@ public class RdeReportActionTest {
}
@Test
void testRunWithLock_fetchFailed_throwsRuntimeException() throws Exception {
class ExpectedThrownException extends RuntimeException {}
when(urlFetchService.fetch(any(HTTPRequest.class))).thenThrow(new ExpectedThrownException());
assertThrows(
ExpectedThrownException.class, () -> createAction().runWithLock(loadRdeReportCursor()));
}
@Test
void testRunWithLock_socketTimeout_doesRetry() throws Exception {
when(httpResponse.getResponseCode()).thenReturn(SC_OK);
when(httpResponse.getContent()).thenReturn(IIRDEA_GOOD_XML.read());
when(urlFetchService.fetch(request.capture()))
.thenThrow(new SocketTimeoutException())
.thenReturn(httpResponse);
createAction().runWithLock(loadRdeReportCursor());
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getContentType()).isEqualTo(PLAIN_TEXT_UTF_8);
assertThat(response.getPayload()).isEqualTo("OK test 2006-06-06T00:00:00.000Z\n");
void testRunWithLock_notAuthorized() throws Exception {
when(httpUrlConnection.getResponseCode()).thenReturn(STATUS_CODE_UNAUTHORIZED);
UrlConnectionException thrown =
assertThrows(
UrlConnectionException.class, () -> createAction().runWithLock(loadRdeReportCursor()));
verify(httpUrlConnection, times(0)).getInputStream();
assertThat(thrown).hasMessageThat().contains("PUT failed");
}
private DateTime loadRdeReportCursor() {
return loadByKey(Cursor.createScopedVKey(RDE_REPORT, registry)).getCursorTime();
}
private static ImmutableMap<String, String> mapifyHeaders(Iterable<HTTPHeader> headers) {
ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<>();
for (HTTPHeader header : headers) {
builder.put(Ascii.toUpperCase(header.getName().replace('-', '_')), header.getValue());
}
return builder.build();
}
private static XjcRdeReportReport parseReport(byte[] data) {
try {
return XjcXmlTransformer.unmarshal(XjcRdeReportReport.class, new ByteArrayInputStream(data));

View File

@@ -14,28 +14,27 @@
package google.registry.reporting.icann;
import static com.google.common.net.MediaType.CSV_UTF_8;
import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_BAD_REQUEST;
import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_OK;
import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_SERVER_ERROR;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.DatabaseHelper.createTld;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.HttpStatusCodes;
import com.google.api.client.http.LowLevelHttpRequest;
import com.google.api.client.http.LowLevelHttpResponse;
import com.google.api.client.testing.http.MockHttpTransport;
import com.google.api.client.testing.http.MockLowLevelHttpRequest;
import com.google.api.client.testing.http.MockLowLevelHttpResponse;
import com.google.api.client.util.Base64;
import com.google.api.client.util.StringUtils;
import com.google.common.io.BaseEncoding;
import com.google.common.io.ByteSource;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import google.registry.testing.FakeUrlConnectionService;
import java.io.ByteArrayOutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@@ -46,103 +45,75 @@ class IcannHttpReporterTest {
private static final ByteSource IIRDEA_GOOD_XML = ReportingTestData.loadBytes("iirdea_good.xml");
private static final ByteSource IIRDEA_BAD_XML = ReportingTestData.loadBytes("iirdea_bad.xml");
private static final byte[] FAKE_PAYLOAD = "test,csv\n1,2".getBytes(UTF_8);
private static final IcannHttpReporter reporter = new IcannHttpReporter();
private MockLowLevelHttpRequest mockRequest;
private final HttpURLConnection connection = mock(HttpURLConnection.class);
private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
private final FakeUrlConnectionService urlConnectionService =
new FakeUrlConnectionService(connection);
@RegisterExtension
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
private MockHttpTransport createMockTransport(
int statusCode, final ByteSource iirdeaResponse) {
return new MockHttpTransport() {
@Override
public LowLevelHttpRequest buildRequest(String method, String url) {
mockRequest =
new MockLowLevelHttpRequest() {
@Override
public LowLevelHttpResponse execute() throws IOException {
MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
response.setStatusCode(statusCode);
response.setContentType(PLAIN_TEXT_UTF_8.toString());
response.setContent(iirdeaResponse.read());
return response;
}
};
mockRequest.setUrl(url);
return mockRequest;
}
};
}
private MockHttpTransport createMockTransport(final ByteSource iirdeaResponse) {
return createMockTransport(HttpStatusCodes.STATUS_CODE_OK, iirdeaResponse);
}
@BeforeEach
void beforeEach() {
void beforeEach() throws Exception {
createTld("test");
createTld("xn--abc123");
}
private IcannHttpReporter createReporter() {
IcannHttpReporter reporter = new IcannHttpReporter();
reporter.httpTransport = createMockTransport(IIRDEA_GOOD_XML);
when(connection.getOutputStream()).thenReturn(outputStream);
when(connection.getResponseCode()).thenReturn(STATUS_CODE_OK);
when(connection.getInputStream()).thenReturn(IIRDEA_GOOD_XML.openBufferedStream());
reporter.urlConnectionService = urlConnectionService;
reporter.password = "fakePass";
reporter.icannTransactionsUrl = "https://fake-transactions.url";
reporter.icannActivityUrl = "https://fake-activity.url";
return reporter;
}
@Test
void testSuccess() throws Exception {
IcannHttpReporter reporter = createReporter();
reporter.send(FAKE_PAYLOAD, "test-transactions-201706.csv");
assertThat(reporter.send(FAKE_PAYLOAD, "test-transactions-201706.csv")).isTrue();
assertThat(mockRequest.getUrl()).isEqualTo("https://fake-transactions.url/test/2017-06");
Map<String, List<String>> headers = mockRequest.getHeaders();
assertThat(urlConnectionService.getConnectedUrls())
.containsExactly(new URL("https://fake-transactions.url/test/2017-06"));
String userPass = "test_ry:fakePass";
String expectedAuth =
String.format("Basic %s", Base64.encodeBase64String(StringUtils.getBytesUtf8(userPass)));
assertThat(headers.get("authorization")).containsExactly(expectedAuth);
assertThat(headers.get("content-type")).containsExactly(CSV_UTF_8.toString());
String.format("Basic %s", BaseEncoding.base64().encode(StringUtils.getBytesUtf8(userPass)));
verify(connection).setRequestProperty("Authorization", expectedAuth);
verify(connection).setRequestProperty("Content-Type", "text/csv; charset=utf-8");
assertThat(outputStream.toByteArray()).isEqualTo(FAKE_PAYLOAD);
}
@Test
void testSuccess_internationalTld() throws Exception {
IcannHttpReporter reporter = createReporter();
reporter.send(FAKE_PAYLOAD, "xn--abc123-transactions-201706.csv");
assertThat(reporter.send(FAKE_PAYLOAD, "xn--abc123-transactions-201706.csv")).isTrue();
assertThat(mockRequest.getUrl()).isEqualTo("https://fake-transactions.url/xn--abc123/2017-06");
Map<String, List<String>> headers = mockRequest.getHeaders();
assertThat(urlConnectionService.getConnectedUrls())
.containsExactly(new URL("https://fake-transactions.url/xn--abc123/2017-06"));
String userPass = "xn--abc123_ry:fakePass";
String expectedAuth =
String.format("Basic %s", Base64.encodeBase64String(StringUtils.getBytesUtf8(userPass)));
assertThat(headers.get("authorization")).containsExactly(expectedAuth);
assertThat(headers.get("content-type")).containsExactly(CSV_UTF_8.toString());
String.format("Basic %s", BaseEncoding.base64().encode(StringUtils.getBytesUtf8(userPass)));
verify(connection).setRequestProperty("Authorization", expectedAuth);
verify(connection).setRequestProperty("Content-Type", "text/csv; charset=utf-8");
assertThat(outputStream.toByteArray()).isEqualTo(FAKE_PAYLOAD);
}
@Test
void testFail_BadIirdeaResponse() throws Exception {
IcannHttpReporter reporter = createReporter();
reporter.httpTransport =
createMockTransport(HttpStatusCodes.STATUS_CODE_BAD_REQUEST, IIRDEA_BAD_XML);
when(connection.getInputStream()).thenReturn(IIRDEA_BAD_XML.openBufferedStream());
when(connection.getResponseCode()).thenReturn(STATUS_CODE_BAD_REQUEST);
assertThat(reporter.send(FAKE_PAYLOAD, "test-transactions-201706.csv")).isFalse();
verify(connection).getInputStream();
}
@Test
void testFail_transportException() {
IcannHttpReporter reporter = createReporter();
reporter.httpTransport =
createMockTransport(HttpStatusCodes.STATUS_CODE_FORBIDDEN, ByteSource.empty());
assertThrows(
HttpResponseException.class,
() -> reporter.send(FAKE_PAYLOAD, "test-transactions-201706.csv"));
void testFail_OtherBadHttpResponse() throws Exception {
when(connection.getResponseCode()).thenReturn(STATUS_CODE_SERVER_ERROR);
assertThat(reporter.send(FAKE_PAYLOAD, "test-transactions-201706.csv")).isFalse();
verify(connection, times(0)).getInputStream();
}
@Test
void testFail_invalidFilename_nonSixDigitYearMonth() {
IcannHttpReporter reporter = createReporter();
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
@@ -156,7 +127,6 @@ class IcannHttpReporterTest {
@Test
void testFail_invalidFilename_notActivityOrTransactions() {
IcannHttpReporter reporter = createReporter();
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
@@ -169,7 +139,6 @@ class IcannHttpReporterTest {
@Test
void testFail_invalidFilename_invalidTldName() {
IcannHttpReporter reporter = createReporter();
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
@@ -183,7 +152,6 @@ class IcannHttpReporterTest {
@Test
void testFail_invalidFilename_tldDoesntExist() {
IcannHttpReporter reporter = createReporter();
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,

View File

@@ -20,6 +20,7 @@ import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.POST;
import static google.registry.request.auth.Auth.AUTH_API_ADMIN;
import static google.registry.request.auth.Auth.AUTH_PUBLIC;
import static google.registry.request.auth.AuthResult.NOT_AUTHENTICATED;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -228,7 +229,7 @@ public final class RequestHandlerTest {
when(req.getMethod()).thenReturn("GET");
when(req.getRequestURI()).thenReturn("/bumblebee");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
.thenReturn(Optional.of(NOT_AUTHENTICATED));
handler.handleRequest(req, rsp);
@@ -242,7 +243,7 @@ public final class RequestHandlerTest {
when(req.getMethod()).thenReturn("POST");
when(req.getRequestURI()).thenReturn("/bumblebee");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
.thenReturn(Optional.of(NOT_AUTHENTICATED));
handler.handleRequest(req, rsp);
@@ -255,7 +256,7 @@ public final class RequestHandlerTest {
when(req.getMethod()).thenReturn("GET");
when(req.getRequestURI()).thenReturn("/bumblebee/hive");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
.thenReturn(Optional.of(NOT_AUTHENTICATED));
handler.handleRequest(req, rsp);
@@ -268,7 +269,7 @@ public final class RequestHandlerTest {
when(req.getMethod()).thenReturn("POST");
when(req.getRequestURI()).thenReturn("/sloth");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
.thenReturn(Optional.of(NOT_AUTHENTICATED));
handler.handleRequest(req, rsp);
@@ -284,7 +285,7 @@ public final class RequestHandlerTest {
when(req.getMethod()).thenReturn("POST");
when(req.getRequestURI()).thenReturn("/sloth/nest");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
.thenReturn(Optional.of(NOT_AUTHENTICATED));
handler.handleRequest(req, rsp);
@@ -296,7 +297,7 @@ public final class RequestHandlerTest {
when(req.getMethod()).thenReturn("GET");
when(req.getRequestURI()).thenReturn("/fail");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
.thenReturn(Optional.of(NOT_AUTHENTICATED));
handler.handleRequest(req, rsp);
@@ -311,7 +312,7 @@ public final class RequestHandlerTest {
when(req.getMethod()).thenReturn("GET");
when(req.getRequestURI()).thenReturn("/failAtConstruction");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
.thenReturn(Optional.of(NOT_AUTHENTICATED));
handler.handleRequest(req, rsp);
@@ -324,7 +325,7 @@ public final class RequestHandlerTest {
when(req.getMethod()).thenReturn("GET");
when(req.getRequestURI()).thenReturn("/bogus");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
.thenReturn(Optional.of(NOT_AUTHENTICATED));
handler.handleRequest(req, rsp);
@@ -336,7 +337,7 @@ public final class RequestHandlerTest {
when(req.getMethod()).thenReturn("POST");
when(req.getRequestURI()).thenReturn("/fail");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
.thenReturn(Optional.of(NOT_AUTHENTICATED));
handler.handleRequest(req, rsp);
@@ -348,7 +349,7 @@ public final class RequestHandlerTest {
when(req.getMethod()).thenReturn("FIREAWAY");
when(req.getRequestURI()).thenReturn("/fail");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
.thenReturn(Optional.of(NOT_AUTHENTICATED));
handler.handleRequest(req, rsp);
@@ -364,7 +365,7 @@ public final class RequestHandlerTest {
when(req.getMethod()).thenReturn("get");
when(req.getRequestURI()).thenReturn("/bumblebee");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
.thenReturn(Optional.of(NOT_AUTHENTICATED));
handler.handleRequest(req, rsp);
@@ -386,7 +387,7 @@ public final class RequestHandlerTest {
when(req.getMethod()).thenReturn("POST");
when(req.getRequestURI()).thenReturn("/safe-sloth");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
.thenReturn(Optional.of(NOT_AUTHENTICATED));
handler.handleRequest(req, rsp);
@@ -399,7 +400,7 @@ public final class RequestHandlerTest {
when(req.getMethod()).thenReturn("GET");
when(req.getRequestURI()).thenReturn("/safe-sloth");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
.thenReturn(Optional.of(NOT_AUTHENTICATED));
handler.handleRequest(req, rsp);
@@ -412,7 +413,7 @@ public final class RequestHandlerTest {
when(req.getMethod()).thenReturn("GET");
when(req.getRequestURI()).thenReturn("/auth/none");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
.thenReturn(Optional.of(NOT_AUTHENTICATED));
handler.handleRequest(req, rsp);
@@ -440,8 +441,7 @@ public final class RequestHandlerTest {
when(req.getMethod()).thenReturn("GET");
when(req.getRequestURI()).thenReturn("/auth/adminUser");
when(requestAuthenticator.authorize(AUTH_API_ADMIN.authSettings(), req))
.thenReturn(
Optional.of(AuthResult.create(AuthLevel.USER, UserAuthInfo.create(testUser, true))));
.thenReturn(Optional.of(AuthResult.createUser(UserAuthInfo.create(testUser, true))));
handler.handleRequest(req, rsp);
@@ -449,7 +449,6 @@ public final class RequestHandlerTest {
assertThat(providedAuthResult.authLevel()).isEqualTo(AuthLevel.USER);
assertThat(providedAuthResult.userAuthInfo()).isPresent();
assertThat(providedAuthResult.userAuthInfo().get().appEngineUser()).hasValue(testUser);
assertThat(providedAuthResult.userAuthInfo().get().oauthTokenInfo()).isEmpty();
assertMetric("/auth/adminUser", GET, AuthLevel.USER, true);
}
}

View File

@@ -15,6 +15,7 @@
package google.registry.request.auth;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.request.auth.AuthResult.NOT_AUTHENTICATED;
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.ADMIN;
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.OWNER;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
@@ -40,7 +41,6 @@ import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.Registrar.State;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.request.auth.AuthSettings.AuthLevel;
import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException;
import google.registry.util.JdkLoggerConfig;
import java.util.Optional;
@@ -75,7 +75,7 @@ class AuthenticatedRegistrarAccessorTest {
private static final AuthResult USER = createAuthResult(false);
private static final AuthResult GAE_ADMIN = createAuthResult(true);
private static final AuthResult NO_USER = AuthResult.create(AuthLevel.NONE);
private static final AuthResult NO_USER = NOT_AUTHENTICATED;
private static final Optional<String> SUPPORT_GROUP = Optional.of("support@registry.example");
/** Registrar ID of a REAL registrar with a RegistrarContact for USER and GAE_ADMIN. */
private static final String REGISTRAR_ID_WITH_CONTACT = "TheRegistrar";
@@ -94,8 +94,7 @@ class AuthenticatedRegistrarAccessorTest {
* @param isAdmin if true, the user is an administrator for the app-engine project.
*/
private static AuthResult createAuthResult(boolean isAdmin) {
return AuthResult.create(
AuthLevel.USER,
return AuthResult.createUser(
UserAuthInfo.create(new User("johndoe@theregistrar.com", "theregistrar.com"), isAdmin));
}
@@ -295,8 +294,7 @@ class AuthenticatedRegistrarAccessorTest {
void testGetRegistrarForUser_inContacts_isNotAdmin_caseInsensitive() throws Exception {
expectGetRegistrarSuccess(
REGISTRAR_ID_WITH_CONTACT,
AuthResult.create(
AuthLevel.USER,
AuthResult.createUser(
UserAuthInfo.create(new User("JohnDoe@theregistrar.com", "theregistrar.com"), false)),
"user JohnDoe@theregistrar.com has [OWNER] access to registrar TheRegistrar");
verify(lazyGroupsConnection).get();
@@ -421,7 +419,7 @@ class AuthenticatedRegistrarAccessorTest {
.setUserRoles(
new UserRoles.Builder().setIsAdmin(true).setGlobalRole(GlobalRole.FTE).build())
.build();
AuthResult authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(consoleUser));
AuthResult authResult = AuthResult.createUser(UserAuthInfo.create(consoleUser));
AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor(
authResult, ADMIN_REGISTRAR_ID, SUPPORT_GROUP, lazyGroupsConnection);
@@ -446,7 +444,7 @@ class AuthenticatedRegistrarAccessorTest {
.setEmailAddress("email@email.com")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_AGENT).build())
.build();
AuthResult authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(consoleUser));
AuthResult authResult = AuthResult.createUser(UserAuthInfo.create(consoleUser));
AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor(
authResult, ADMIN_REGISTRAR_ID, SUPPORT_GROUP, lazyGroupsConnection);
@@ -471,7 +469,7 @@ class AuthenticatedRegistrarAccessorTest {
RegistrarRole.ACCOUNT_MANAGER))
.build())
.build();
AuthResult authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(consoleUser));
AuthResult authResult = AuthResult.createUser(UserAuthInfo.create(consoleUser));
AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor(
authResult, ADMIN_REGISTRAR_ID, SUPPORT_GROUP, lazyGroupsConnection);

View File

@@ -18,7 +18,6 @@ import static com.google.common.net.HttpHeaders.AUTHORIZATION;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.request.auth.AuthModule.BEARER_PREFIX;
import static google.registry.request.auth.AuthModule.IAP_HEADER_NAME;
import static google.registry.request.auth.AuthModule.PROXY_HEADER_NAME;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -92,9 +91,8 @@ public class OidcTokenAuthenticationMechanismTest {
@Test
void testAuthResultBypass() {
OidcTokenAuthenticationMechanism.setAuthResultForTesting(AuthResult.create(AuthLevel.APP));
assertThat(authenticationMechanism.authenticate(null))
.isEqualTo(AuthResult.create(AuthLevel.APP));
OidcTokenAuthenticationMechanism.setAuthResultForTesting(AuthResult.NOT_AUTHENTICATED);
assertThat(authenticationMechanism.authenticate(null)).isEqualTo(AuthResult.NOT_AUTHENTICATED);
}
@Test
@@ -169,16 +167,10 @@ public class OidcTokenAuthenticationMechanismTest {
void testRegular_tokenExtractor() throws Exception {
useRegularOidcMechanism();
// The token does not have the "Bearer " prefix.
when(request.getHeader(PROXY_HEADER_NAME)).thenReturn(rawToken);
when(request.getHeader(AUTHORIZATION)).thenReturn(rawToken);
assertThat(authenticationMechanism.tokenExtractor.extract(request)).isNull();
// The token is in the correct format.
when(request.getHeader(PROXY_HEADER_NAME))
.thenReturn(String.format("%s%s", BEARER_PREFIX, rawToken));
assertThat(authenticationMechanism.tokenExtractor.extract(request)).isEqualTo(rawToken);
// The token is in the correct format, and under the alternative header.
when(request.getHeader(PROXY_HEADER_NAME)).thenReturn(null);
when(request.getHeader(AUTHORIZATION))
.thenReturn(String.format("%s%s", BEARER_PREFIX, rawToken));
assertThat(authenticationMechanism.tokenExtractor.extract(request)).isEqualTo(rawToken);

View File

@@ -14,361 +14,276 @@
package google.registry.request.auth;
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.request.auth.AuthResult.NOT_AUTHENTICATED;
import static google.registry.request.auth.AuthSettings.AuthLevel.APP;
import static google.registry.request.auth.AuthSettings.AuthLevel.NONE;
import static google.registry.request.auth.AuthSettings.AuthLevel.USER;
import static google.registry.request.auth.AuthSettings.AuthMethod.API;
import static google.registry.request.auth.AuthSettings.AuthMethod.LEGACY;
import static google.registry.request.auth.AuthSettings.UserPolicy.ADMIN;
import static google.registry.request.auth.AuthSettings.UserPolicy.PUBLIC;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
import google.registry.request.auth.AuthSettings.AuthLevel;
import google.registry.request.auth.AuthSettings.AuthMethod;
import google.registry.request.auth.AuthSettings.UserPolicy;
import google.registry.security.XsrfTokenManager;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeOAuthService;
import google.registry.testing.FakeUserService;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link RequestAuthenticator}. */
class RequestAuthenticatorTest {
@RegisterExtension
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
private static final AuthResult APP_AUTH = AuthResult.createApp("app@registry.example");
private static final AuthSettings AUTH_NONE =
AuthSettings.create(ImmutableList.of(AuthMethod.API), AuthLevel.NONE, UserPolicy.PUBLIC);
private static final AuthResult USER_PUBLIC_AUTH =
AuthResult.createUser(
UserAuthInfo.create(
new User.Builder()
.setEmailAddress("user@registry.example")
.setUserRoles(
new UserRoles.Builder()
.setIsAdmin(false)
.setGlobalRole(GlobalRole.NONE)
.build())
.build()));
private static final AuthSettings AUTH_ANY_USER_ANY_METHOD =
AuthSettings.create(
ImmutableList.of(AuthMethod.API, AuthMethod.LEGACY), AuthLevel.USER, UserPolicy.PUBLIC);
private static final AuthResult USER_ADMIN_AUTH =
AuthResult.createUser(
UserAuthInfo.create(
new User.Builder()
.setEmailAddress("admin@registry.example")
.setUserRoles(
new UserRoles.Builder()
.setIsAdmin(true)
.setGlobalRole(GlobalRole.FTE)
.build())
.build()));
private static final AuthSettings AUTH_ANY_USER_NO_LEGACY =
AuthSettings.create(ImmutableList.of(AuthMethod.API), AuthLevel.USER, UserPolicy.PUBLIC);
private static final AuthSettings AUTH_ADMIN_USER_ANY_METHOD =
AuthSettings.create(
ImmutableList.of(AuthMethod.API, AuthMethod.LEGACY), AuthLevel.USER, UserPolicy.ADMIN);
private static final AuthSettings AUTH_NO_METHODS =
AuthSettings.create(ImmutableList.of(), AuthLevel.APP, UserPolicy.PUBLIC);
private static final AuthSettings AUTH_WRONG_METHOD_ORDERING =
AuthSettings.create(
ImmutableList.of(AuthMethod.LEGACY, AuthMethod.API), AuthLevel.APP, UserPolicy.PUBLIC);
private static final AuthSettings AUTH_DUPLICATE_METHODS =
AuthSettings.create(
ImmutableList.of(AuthMethod.API, AuthMethod.API), AuthLevel.APP, UserPolicy.PUBLIC);
private static final AuthSettings AUTH_NONE_REQUIRES_ADMIN =
AuthSettings.create(ImmutableList.of(AuthMethod.API), AuthLevel.NONE, UserPolicy.ADMIN);
private final UserService mockUserService = mock(UserService.class);
private final HttpServletRequest req = mock(HttpServletRequest.class);
private final User testUser = new User("test@google.com", "test@google.com");
private final FakeUserService fakeUserService = new FakeUserService();
private final XsrfTokenManager xsrfTokenManager =
new XsrfTokenManager(new FakeClock(), fakeUserService);
private final FakeOAuthService fakeOAuthService =
new FakeOAuthService(
false /* isOAuthEnabled */,
testUser,
false /* isUserAdmin */,
"test-client-id",
ImmutableList.of("test-scope1", "test-scope2", "nontest-scope"));
private final AuthenticationMechanism apiAuthenticationMechanism1 =
mock(AuthenticationMechanism.class);
private final AuthenticationMechanism apiAuthenticationMechanism2 =
mock(AuthenticationMechanism.class);
private final LegacyAuthenticationMechanism legacyAuthenticationMechanism =
mock(LegacyAuthenticationMechanism.class);
private Optional<AuthResult> authorize(AuthLevel authLevel, UserPolicy userPolicy) {
return new RequestAuthenticator(
ImmutableList.of(apiAuthenticationMechanism1, apiAuthenticationMechanism2),
legacyAuthenticationMechanism)
.authorize(AuthSettings.create(ImmutableList.of(API, LEGACY), authLevel, userPolicy), req);
}
private AuthResult authenticate(AuthMethod... methods) {
return new RequestAuthenticator(
ImmutableList.of(apiAuthenticationMechanism1, apiAuthenticationMechanism2),
legacyAuthenticationMechanism)
.authenticate(AuthSettings.create(ImmutableList.copyOf(methods), NONE, PUBLIC), req);
}
@BeforeEach
void beforeEach() {
when(req.getMethod()).thenReturn("POST");
}
private RequestAuthenticator createRequestAuthenticator(UserService userService) {
return new RequestAuthenticator(
ImmutableList.of(
new OAuthAuthenticationMechanism(
fakeOAuthService,
ImmutableSet.of("test-scope1", "test-scope2", "test-scope3"),
ImmutableSet.of("test-scope1", "test-scope2"),
ImmutableSet.of("test-client-id", "other-test-client-id"))),
new LegacyAuthenticationMechanism(userService, xsrfTokenManager));
}
private Optional<AuthResult> runTest(UserService userService, AuthSettings auth) {
return createRequestAuthenticator(userService).authorize(auth, req);
when(apiAuthenticationMechanism1.authenticate(req)).thenReturn(NOT_AUTHENTICATED);
when(apiAuthenticationMechanism2.authenticate(req)).thenReturn(NOT_AUTHENTICATED);
when(legacyAuthenticationMechanism.authenticate(req)).thenReturn(NOT_AUTHENTICATED);
}
@Test
void testNoAuthNeeded_noneFound() {
Optional<AuthResult> authResult = runTest(mockUserService, AUTH_NONE);
verifyNoInteractions(mockUserService);
assertThat(authResult).isPresent();
assertThat(authResult.get().authLevel()).isEqualTo(AuthLevel.NONE);
void testAuthorize_noneRequired() {
for (AuthResult resultFound :
ImmutableList.of(NOT_AUTHENTICATED, APP_AUTH, USER_ADMIN_AUTH, USER_PUBLIC_AUTH)) {
when(apiAuthenticationMechanism1.authenticate(req)).thenReturn(resultFound);
assertThat(authorize(NONE, PUBLIC)).hasValue(resultFound);
}
}
@Test
void testAnyUserAnyMethod_notLoggedIn() {
Optional<AuthResult> authResult = runTest(fakeUserService, AUTH_ANY_USER_ANY_METHOD);
void testAuthorize_appPublicRequired() {
authorize(APP, PUBLIC);
assertThat(authorize(APP, PUBLIC)).isEmpty();
assertThat(authResult).isEmpty();
for (AuthResult resultFound : ImmutableList.of(APP_AUTH, USER_ADMIN_AUTH, USER_PUBLIC_AUTH)) {
when(apiAuthenticationMechanism1.authenticate(req)).thenReturn(resultFound);
assertThat(authorize(APP, PUBLIC)).hasValue(resultFound);
}
}
@Test
void testAnyUserAnyMethod_xsrfFailure() {
fakeUserService.setUser(testUser, false);
void testAuthorize_appAdminRequired() {
for (AuthResult resultFound : ImmutableList.of(NOT_AUTHENTICATED, USER_PUBLIC_AUTH)) {
when(apiAuthenticationMechanism1.authenticate(req)).thenReturn(resultFound);
assertThat(authorize(APP, ADMIN)).isEmpty();
}
Optional<AuthResult> authResult = runTest(fakeUserService, AUTH_ANY_USER_ANY_METHOD);
assertThat(authResult).isEmpty();
for (AuthResult resultFound : ImmutableList.of(APP_AUTH, USER_ADMIN_AUTH)) {
when(apiAuthenticationMechanism1.authenticate(req)).thenReturn(resultFound);
assertThat(authorize(APP, ADMIN)).hasValue(resultFound);
}
}
@Test
void testAnyUserAnyMethod_success() {
fakeUserService.setUser(testUser, false /* isAdmin */);
when(req.getHeader(XsrfTokenManager.X_CSRF_TOKEN))
.thenReturn(xsrfTokenManager.generateToken(testUser.getEmail()));
void testAuthorize_userPublicRequired() {
for (AuthResult resultFound : ImmutableList.of(NOT_AUTHENTICATED, APP_AUTH)) {
when(apiAuthenticationMechanism1.authenticate(req)).thenReturn(resultFound);
assertThat(authorize(USER, PUBLIC)).isEmpty();
}
Optional<AuthResult> authResult = runTest(fakeUserService, AUTH_ANY_USER_ANY_METHOD);
assertThat(authResult).isPresent();
assertThat(authResult.get().authLevel()).isEqualTo(AuthLevel.USER);
assertThat(authResult.get().userAuthInfo()).isPresent();
assertThat(authResult.get().userAuthInfo().get().appEngineUser()).hasValue(testUser);
assertThat(authResult.get().userAuthInfo().get().isUserAdmin()).isFalse();
assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo()).isEmpty();
for (AuthResult resultFound : ImmutableList.of(USER_PUBLIC_AUTH, USER_ADMIN_AUTH)) {
when(apiAuthenticationMechanism1.authenticate(req)).thenReturn(resultFound);
assertThat(authorize(USER, PUBLIC)).hasValue(resultFound);
}
}
@Test
void testAnyUserAnyMethod_xsrfNotRequiredForGet() {
fakeUserService.setUser(testUser, false);
when(req.getMethod()).thenReturn("GET");
void testAuthorize_userAdminRequired() {
for (AuthResult resultFound : ImmutableList.of(NOT_AUTHENTICATED, APP_AUTH, USER_PUBLIC_AUTH)) {
when(apiAuthenticationMechanism1.authenticate(req)).thenReturn(resultFound);
assertThat(authorize(USER, ADMIN)).isEmpty();
}
Optional<AuthResult> authResult = runTest(fakeUserService, AUTH_ANY_USER_ANY_METHOD);
assertThat(authResult).isPresent();
assertThat(authResult.get().authLevel()).isEqualTo(AuthLevel.USER);
assertThat(authResult.get().userAuthInfo()).isPresent();
assertThat(authResult.get().userAuthInfo().get().appEngineUser()).hasValue(testUser);
assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo()).isEmpty();
when(apiAuthenticationMechanism1.authenticate(req)).thenReturn(USER_ADMIN_AUTH);
assertThat(authorize(USER, ADMIN)).hasValue(USER_ADMIN_AUTH);
}
@Test
void testAdminUserAnyMethod_notLoggedIn() {
Optional<AuthResult> authResult = runTest(fakeUserService, AUTH_ADMIN_USER_ANY_METHOD);
assertThat(authResult).isEmpty();
void testAuthenticate_apiFirst() {
when(apiAuthenticationMechanism1.authenticate(req)).thenReturn(APP_AUTH);
assertThat(authenticate(API, LEGACY)).isEqualTo(APP_AUTH);
verify(apiAuthenticationMechanism1).authenticate(req);
verifyNoMoreInteractions(apiAuthenticationMechanism1);
verifyNoMoreInteractions(apiAuthenticationMechanism2);
verifyNoMoreInteractions(legacyAuthenticationMechanism);
}
@Test
void testAdminUserAnyMethod_notAdminUser() {
fakeUserService.setUser(testUser, false /* isAdmin */);
Optional<AuthResult> authResult = runTest(fakeUserService, AUTH_ADMIN_USER_ANY_METHOD);
assertThat(authResult).isEmpty();
void testAuthenticate_apiSecond() {
when(apiAuthenticationMechanism2.authenticate(req)).thenReturn(APP_AUTH);
assertThat(authenticate(API, LEGACY)).isEqualTo(APP_AUTH);
verify(apiAuthenticationMechanism1).authenticate(req);
verify(apiAuthenticationMechanism2).authenticate(req);
verifyNoMoreInteractions(apiAuthenticationMechanism1);
verifyNoMoreInteractions(apiAuthenticationMechanism2);
verifyNoMoreInteractions(legacyAuthenticationMechanism);
}
@Test
void testAdminUserAnyMethod_xsrfFailure() {
fakeUserService.setUser(testUser, true);
Optional<AuthResult> authResult = runTest(fakeUserService, AUTH_ADMIN_USER_ANY_METHOD);
assertThat(authResult).isEmpty();
void testAuthenticate_legacy() {
when(legacyAuthenticationMechanism.authenticate(req)).thenReturn(APP_AUTH);
assertThat(authenticate(API, LEGACY)).isEqualTo(APP_AUTH);
verify(apiAuthenticationMechanism1).authenticate(req);
verify(apiAuthenticationMechanism2).authenticate(req);
verify(legacyAuthenticationMechanism).authenticate(req);
verifyNoMoreInteractions(apiAuthenticationMechanism1);
verifyNoMoreInteractions(apiAuthenticationMechanism2);
verifyNoMoreInteractions(legacyAuthenticationMechanism);
}
@Test
void testAdminUserAnyMethod_success() {
fakeUserService.setUser(testUser, true /* isAdmin */);
when(req.getHeader(XsrfTokenManager.X_CSRF_TOKEN))
.thenReturn(xsrfTokenManager.generateToken(testUser.getEmail()));
Optional<AuthResult> authResult = runTest(fakeUserService, AUTH_ADMIN_USER_ANY_METHOD);
assertThat(authResult).isPresent();
assertThat(authResult.get().authLevel()).isEqualTo(AuthLevel.USER);
assertThat(authResult.get().userAuthInfo()).isPresent();
assertThat(authResult.get().userAuthInfo().get().appEngineUser()).hasValue(testUser);
assertThat(authResult.get().userAuthInfo().get().isUserAdmin()).isTrue();
assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo()).isEmpty();
void testAuthenticate_returnFirstResult() {
// API auth 2 returns an authenticted auth result, so we don't bother trying the next auth
// (legacy auth).
when(apiAuthenticationMechanism2.authenticate(req)).thenReturn(APP_AUTH);
when(legacyAuthenticationMechanism.authenticate(req)).thenReturn(USER_PUBLIC_AUTH);
assertThat(authenticate(API, LEGACY)).isEqualTo(APP_AUTH);
verify(apiAuthenticationMechanism1).authenticate(req);
verify(apiAuthenticationMechanism2).authenticate(req);
verifyNoMoreInteractions(apiAuthenticationMechanism1);
verifyNoMoreInteractions(apiAuthenticationMechanism2);
verifyNoMoreInteractions(legacyAuthenticationMechanism);
}
@Test
void testOAuth_success() {
fakeOAuthService.setUser(testUser);
fakeOAuthService.setOAuthEnabled(true);
when(req.getHeader(AUTHORIZATION)).thenReturn("Bearer TOKEN");
Optional<AuthResult> authResult = runTest(fakeUserService, AUTH_ANY_USER_NO_LEGACY);
assertThat(authResult).isPresent();
assertThat(authResult.get().authLevel()).isEqualTo(AuthLevel.USER);
assertThat(authResult.get().userAuthInfo()).isPresent();
assertThat(authResult.get().userAuthInfo().get().appEngineUser()).hasValue(testUser);
assertThat(authResult.get().userAuthInfo().get().isUserAdmin()).isFalse();
assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo()).isPresent();
assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo().get().authorizedScopes())
.containsAtLeast("test-scope1", "test-scope2");
assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo().get().oauthClientId())
.isEqualTo("test-client-id");
assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo().get().rawAccessToken())
.isEqualTo("TOKEN");
void testAuthenticate_notAuthenticated() {
assertThat(authenticate(API, LEGACY)).isEqualTo(NOT_AUTHENTICATED);
verify(apiAuthenticationMechanism1).authenticate(req);
verify(apiAuthenticationMechanism2).authenticate(req);
verify(legacyAuthenticationMechanism).authenticate(req);
verifyNoMoreInteractions(apiAuthenticationMechanism1);
verifyNoMoreInteractions(apiAuthenticationMechanism2);
verifyNoMoreInteractions(legacyAuthenticationMechanism);
}
@Test
void testOAuthAdmin_success() {
fakeOAuthService.setUser(testUser);
fakeOAuthService.setUserAdmin(true);
fakeOAuthService.setOAuthEnabled(true);
when(req.getHeader(AUTHORIZATION)).thenReturn("Bearer TOKEN");
Optional<AuthResult> authResult = runTest(fakeUserService, AUTH_ANY_USER_NO_LEGACY);
assertThat(authResult).isPresent();
assertThat(authResult.get().authLevel()).isEqualTo(AuthLevel.USER);
assertThat(authResult.get().userAuthInfo()).isPresent();
assertThat(authResult.get().userAuthInfo().get().appEngineUser()).hasValue(testUser);
assertThat(authResult.get().userAuthInfo().get().isUserAdmin()).isTrue();
assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo()).isPresent();
assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo().get().authorizedScopes())
.containsAtLeast("test-scope1", "test-scope2");
assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo().get().oauthClientId())
.isEqualTo("test-client-id");
assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo().get().rawAccessToken())
.isEqualTo("TOKEN");
void testAuthenticate_apiOnly() {
when(legacyAuthenticationMechanism.authenticate(req)).thenReturn(USER_PUBLIC_AUTH);
assertThat(authenticate(API)).isEqualTo(NOT_AUTHENTICATED);
verify(apiAuthenticationMechanism1).authenticate(req);
verify(apiAuthenticationMechanism2).authenticate(req);
verifyNoMoreInteractions(apiAuthenticationMechanism1);
verifyNoMoreInteractions(apiAuthenticationMechanism2);
verifyNoMoreInteractions(legacyAuthenticationMechanism);
}
@Test
void testOAuthMissingAuthenticationToken_failure() {
fakeOAuthService.setUser(testUser);
fakeOAuthService.setOAuthEnabled(true);
Optional<AuthResult> authResult = runTest(fakeUserService, AUTH_ANY_USER_NO_LEGACY);
assertThat(authResult).isEmpty();
void testAuthenticate_legacyOnly() {
when(apiAuthenticationMechanism1.authenticate(req)).thenReturn(USER_PUBLIC_AUTH);
assertThat(authenticate(LEGACY)).isEqualTo(NOT_AUTHENTICATED);
verify(legacyAuthenticationMechanism).authenticate(req);
verifyNoMoreInteractions(apiAuthenticationMechanism1);
verifyNoMoreInteractions(apiAuthenticationMechanism2);
verifyNoMoreInteractions(legacyAuthenticationMechanism);
}
@Test
void testOAuthClientIdMismatch_failure() {
fakeOAuthService.setUser(testUser);
fakeOAuthService.setOAuthEnabled(true);
fakeOAuthService.setClientId("wrong-client-id");
when(req.getHeader(AUTHORIZATION)).thenReturn("Bearer TOKEN");
Optional<AuthResult> authResult = runTest(fakeUserService, AUTH_ANY_USER_NO_LEGACY);
assertThat(authResult).isEmpty();
}
@Test
void testOAuthNoScopes_failure() {
fakeOAuthService.setUser(testUser);
fakeOAuthService.setOAuthEnabled(true);
fakeOAuthService.setAuthorizedScopes();
when(req.getHeader(AUTHORIZATION)).thenReturn("Bearer TOKEN");
Optional<AuthResult> authResult = runTest(fakeUserService, AUTH_ANY_USER_NO_LEGACY);
assertThat(authResult).isEmpty();
}
@Test
void testOAuthMissingScope_failure() {
fakeOAuthService.setUser(testUser);
fakeOAuthService.setOAuthEnabled(true);
fakeOAuthService.setAuthorizedScopes("test-scope1", "test-scope3");
when(req.getHeader(AUTHORIZATION)).thenReturn("Bearer TOKEN");
Optional<AuthResult> authResult = runTest(fakeUserService, AUTH_ANY_USER_NO_LEGACY);
assertThat(authResult).isEmpty();
}
@Test
void testOAuthExtraScope_success() {
fakeOAuthService.setUser(testUser);
fakeOAuthService.setOAuthEnabled(true);
fakeOAuthService.setAuthorizedScopes("test-scope1", "test-scope2", "test-scope3");
when(req.getHeader(AUTHORIZATION)).thenReturn("Bearer TOKEN");
Optional<AuthResult> authResult = runTest(fakeUserService, AUTH_ANY_USER_NO_LEGACY);
assertThat(authResult).isPresent();
assertThat(authResult.get().authLevel()).isEqualTo(AuthLevel.USER);
assertThat(authResult.get().userAuthInfo()).isPresent();
assertThat(authResult.get().userAuthInfo().get().appEngineUser()).hasValue(testUser);
assertThat(authResult.get().userAuthInfo().get().isUserAdmin()).isFalse();
assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo()).isPresent();
assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo().get().authorizedScopes())
.containsAtLeast("test-scope1", "test-scope2", "test-scope3");
assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo().get().oauthClientId())
.isEqualTo("test-client-id");
assertThat(authResult.get().userAuthInfo().get().oauthTokenInfo().get().rawAccessToken())
.isEqualTo("TOKEN");
}
@Test
void testAnyUserNoLegacy_failureWithLegacyUser() {
fakeUserService.setUser(testUser, false /* isAdmin */);
Optional<AuthResult> authResult = runTest(fakeUserService, AUTH_ANY_USER_NO_LEGACY);
assertThat(authResult).isEmpty();
}
@Test
void testCheckAuthConfig_noMethods_failure() {
void testFailure_checkAuthConfig_noMethods() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> RequestAuthenticator.checkAuthConfig(AUTH_NO_METHODS));
() ->
RequestAuthenticator.checkAuthConfig(
AuthSettings.create(ImmutableList.of(), NONE, PUBLIC)));
assertThat(thrown).hasMessageThat().contains("Must specify at least one auth method");
}
@Test
void testCheckAuthConfig_wrongMethodOrdering_failure() {
void testFailure_checkAuthConfig_wrongMethodOrder() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> RequestAuthenticator.checkAuthConfig(AUTH_WRONG_METHOD_ORDERING));
() ->
RequestAuthenticator.checkAuthConfig(
AuthSettings.create(ImmutableList.of(LEGACY, API), NONE, PUBLIC)));
assertThat(thrown)
.hasMessageThat()
.contains("Auth methods must be unique and strictly in order - API, LEGACY");
}
@Test
void testCheckAuthConfig_noneAuthLevelRequiresAdmin_failure() {
void testFailure_CheckAuthConfig_duplicateMethods() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> RequestAuthenticator.checkAuthConfig(AUTH_NONE_REQUIRES_ADMIN));
() ->
RequestAuthenticator.checkAuthConfig(
AuthSettings.create(ImmutableList.of(API, API), NONE, PUBLIC)));
assertThat(thrown)
.hasMessageThat()
.contains("Auth methods must be unique and strictly in order - API, LEGACY");
}
@Test
void testFailure_checkAuthConfig_noneAuthLevelRequiresAdmin() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
RequestAuthenticator.checkAuthConfig(
AuthSettings.create(ImmutableList.of(API, LEGACY), NONE, ADMIN)));
assertThat(thrown)
.hasMessageThat()
.contains("Actions with minimal auth level at NONE should not specify ADMIN user policy");
}
@Test
void testCheckAuthConfig_DuplicateMethods_failure() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> RequestAuthenticator.checkAuthConfig(AUTH_DUPLICATE_METHODS));
assertThat(thrown)
.hasMessageThat()
.contains("Auth methods must be unique and strictly in order - API, LEGACY");
}
}

View File

@@ -16,13 +16,15 @@ package google.registry.security;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.common.base.Splitter;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeUserService;
import org.joda.time.Duration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -37,14 +39,16 @@ class XsrfTokenManagerTest {
private final User testUser = new User("test@example.com", "test@example.com");
private final FakeClock clock = new FakeClock(START_OF_TIME);
private final FakeUserService userService = new FakeUserService();
private final UserService userService = mock(UserService.class);
private final XsrfTokenManager xsrfTokenManager = new XsrfTokenManager(clock, userService);
private String token;
@BeforeEach
void beforeEach() {
userService.setUser(testUser, false);
when(userService.isUserLoggedIn()).thenReturn(true);
when(userService.getCurrentUser()).thenReturn(testUser);
when(userService.isUserAdmin()).thenReturn(false);
token = xsrfTokenManager.generateToken(testUser.getEmail());
}

View File

@@ -84,7 +84,7 @@ public final class RegistryTestServer {
private final TestServer server;
/** @see TestServer#TestServer(HostAndPort, ImmutableMap, ImmutableList, ImmutableList) */
/** @see TestServer#TestServer(HostAndPort, ImmutableMap, ImmutableList) */
public RegistryTestServer(HostAndPort address) {
server = new TestServer(address, RUNFILES, ROUTES);
}
@@ -104,7 +104,7 @@ public final class RegistryTestServer {
server.stop();
}
/** @see TestServer#getUrl(java.lang.String) */
/** @see TestServer#getUrl(String) */
public URL getUrl(String path) {
return server.getUrl(path);
}

View File

@@ -25,7 +25,6 @@ import google.registry.model.console.UserRoles;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTransactionManagerExtension;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthSettings.AuthLevel;
import google.registry.request.auth.OidcTokenAuthenticationMechanism;
import google.registry.request.auth.UserAuthInfo;
import google.registry.testing.UserInfo;
@@ -148,7 +147,7 @@ public final class RegistryTestServerMain {
.setRegistryLockPassword("registryLockPassword")
.build();
OidcTokenAuthenticationMechanism.setAuthResultForTesting(
AuthResult.create(AuthLevel.USER, UserAuthInfo.create(user)));
AuthResult.createUser(UserAuthInfo.create(user)));
new JpaTestExtensions.Builder().buildIntegrationTestExtension().beforeEach(null);
JpaTransactionManagerExtension.loadInitialData();
System.out.printf("%sLoading fixtures...%s\n", BLUE, RESET);

View File

@@ -1,130 +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.testing;
import com.google.appengine.api.oauth.OAuthRequestException;
import com.google.appengine.api.oauth.OAuthService;
import com.google.appengine.api.users.User;
import com.google.common.collect.ImmutableList;
import java.util.List;
/** A fake {@link OAuthService} implementation for testing. */
public class FakeOAuthService implements OAuthService {
private boolean isOAuthEnabled;
private User currentUser;
private boolean isUserAdmin;
private String clientId;
private ImmutableList<String> authorizedScopes;
public FakeOAuthService(
boolean isOAuthEnabled,
User currentUser,
boolean isUserAdmin,
String clientId,
List<String> authorizedScopes) {
this.isOAuthEnabled = isOAuthEnabled;
this.currentUser = currentUser;
this.isUserAdmin = isUserAdmin;
this.clientId = clientId;
this.authorizedScopes = ImmutableList.copyOf(authorizedScopes);
}
public void setOAuthEnabled(boolean isOAuthEnabled) {
this.isOAuthEnabled = isOAuthEnabled;
}
public void setUser(User currentUser) {
this.currentUser = currentUser;
}
public void setUserAdmin(boolean isUserAdmin) {
this.isUserAdmin = isUserAdmin;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public void setAuthorizedScopes(String... scopes) {
this.authorizedScopes = ImmutableList.copyOf(scopes);
}
@Override
public User getCurrentUser() throws OAuthRequestException {
if (!isOAuthEnabled) {
throw new OAuthRequestException("invalid OAuth request");
}
return currentUser;
}
@Override
public User getCurrentUser(String scope) throws OAuthRequestException {
return getCurrentUser();
}
@Override
public User getCurrentUser(String... scopes) throws OAuthRequestException {
return getCurrentUser();
}
@Override
public boolean isUserAdmin() throws OAuthRequestException {
if (!isOAuthEnabled) {
throw new OAuthRequestException("invalid OAuth request");
}
return isUserAdmin;
}
@Override
public boolean isUserAdmin(String scope) throws OAuthRequestException {
return isUserAdmin();
}
@Override
public boolean isUserAdmin(String... scopes) throws OAuthRequestException {
return isUserAdmin();
}
@Override
public String getClientId(String scope) throws OAuthRequestException {
if (!isOAuthEnabled) {
throw new OAuthRequestException("invalid OAuth request");
}
return clientId;
}
@Override
public String getClientId(String... scopes) throws OAuthRequestException {
if (!isOAuthEnabled) {
throw new OAuthRequestException("invalid OAuth request");
}
return clientId;
}
@Override
public String[] getAuthorizedScopes(String... scopes) throws OAuthRequestException {
if (!isOAuthEnabled) {
throw new OAuthRequestException("invalid OAuth request");
}
return authorizedScopes.toArray(new String[0]);
}
@Deprecated
@Override
public String getOAuthConsumerKey() {
throw new UnsupportedOperationException();
}
}

View File

@@ -16,6 +16,7 @@ package google.registry.testing;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
import google.registry.request.UrlConnectionService;
import java.net.HttpURLConnection;
import java.net.URL;
@@ -26,15 +27,10 @@ import java.util.List;
public class FakeUrlConnectionService implements UrlConnectionService {
private final HttpURLConnection mockConnection;
private final List<URL> connectedUrls;
private final List<URL> connectedUrls = new ArrayList<>();
public FakeUrlConnectionService(HttpURLConnection mockConnection) {
this(mockConnection, new ArrayList<>());
}
public FakeUrlConnectionService(HttpURLConnection mockConnection, List<URL> connectedUrls) {
this.mockConnection = mockConnection;
this.connectedUrls = connectedUrls;
}
@Override
@@ -43,4 +39,8 @@ public class FakeUrlConnectionService implements UrlConnectionService {
when(mockConnection.getURL()).thenReturn(url);
return mockConnection;
}
public ImmutableList<URL> getConnectedUrls() {
return ImmutableList.copyOf(connectedUrls);
}
}

View File

@@ -1,76 +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.testing;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import google.registry.model.annotations.DeleteAfterMigration;
import java.util.Set;
import javax.annotation.Nullable;
/** Fake implementation of {@link UserService} for testing. */
@DeleteAfterMigration
public class FakeUserService implements UserService {
@Nullable private User user = null;
private boolean isAdmin = false;
public void setUser(@Nullable User user, boolean isAdmin) {
this.user = user;
this.isAdmin = isAdmin;
}
@Override
public String createLoginURL(String destinationURL) {
return String.format("/login?dest=%s", destinationURL);
}
@Override
public String createLoginURL(String destinationURL, String authDomain) {
return createLoginURL(destinationURL);
}
@Deprecated
@Override
public String createLoginURL(String destinationURL, String authDomain, String federatedIdentity,
Set<String> attributesRequest) {
throw new UnsupportedOperationException();
}
@Override
public String createLogoutURL(String destinationURL) {
return String.format("/logout?dest=%s", destinationURL);
}
@Override
public String createLogoutURL(String destinationURL, String authDomain) {
return createLogoutURL(destinationURL);
}
@Override
public boolean isUserLoggedIn() {
return user != null;
}
@Override
public boolean isUserAdmin() {
return isAdmin;
}
@Override
public User getCurrentUser() {
return user;
}
}

View File

@@ -25,9 +25,7 @@ import google.registry.testing.FakeClock;
import google.registry.testing.FakeUrlConnectionService;
import google.registry.testing.TestCacheExtension;
import java.net.HttpURLConnection;
import java.net.URL;
import java.time.Duration;
import java.util.ArrayList;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
@@ -55,9 +53,8 @@ abstract class TmchActionTestCase {
final Marksdb marksdb = new Marksdb();
protected final HttpURLConnection httpUrlConnection = mock(HttpURLConnection.class);
protected final ArrayList<URL> connectedUrls = new ArrayList<>();
protected FakeUrlConnectionService urlConnectionService =
new FakeUrlConnectionService(httpUrlConnection, connectedUrls);
new FakeUrlConnectionService(httpUrlConnection);
@BeforeEach
public void beforeEachTmchActionTestCase() throws Exception {

View File

@@ -56,7 +56,8 @@ class TmchCrlActionTest extends TmchActionTestCase {
readResourceBytes(TmchCertificateAuthority.class, "icann-tmch-pilot.crl").read()));
newTmchCrlAction(TmchCaMode.PILOT).run();
verify(httpUrlConnection).getInputStream();
assertThat(connectedUrls).containsExactly(new URL("https://sloth.lol/tmch.crl"));
assertThat(urlConnectionService.getConnectedUrls())
.containsExactly(new URL("https://sloth.lol/tmch.crl"));
}
@Test

View File

@@ -49,7 +49,10 @@ class TmchDnlActionTest extends TmchActionTestCase {
.thenReturn(new ByteArrayInputStream(TmchTestData.loadBytes("dnl/dnl-latest.sig").read()));
newTmchDnlAction().run();
verify(httpUrlConnection, times(2)).getInputStream();
assertThat(connectedUrls.stream().map(URL::toString).collect(toImmutableList()))
assertThat(
urlConnectionService.getConnectedUrls().stream()
.map(URL::toString)
.collect(toImmutableList()))
.containsExactly(MARKSDB_URL + "/dnl/dnl-latest.csv", MARKSDB_URL + "/dnl/dnl-latest.sig");
// Make sure the contents of testdata/dnl-latest.csv got inserted into the database.

View File

@@ -50,7 +50,10 @@ class TmchSmdrlActionTest extends TmchActionTestCase {
.thenReturn(new ByteArrayInputStream(loadBytes("smdrl/smdrl-latest.sig").read()));
newTmchSmdrlAction().run();
verify(httpUrlConnection, times(2)).getInputStream();
assertThat(connectedUrls.stream().map(URL::toString).collect(toImmutableList()))
assertThat(
urlConnectionService.getConnectedUrls().stream()
.map(URL::toString)
.collect(toImmutableList()))
.containsExactly(
MARKSDB_URL + "/smdrl/smdrl-latest.csv", MARKSDB_URL + "/smdrl/smdrl-latest.sig");
smdrl = SignedMarkRevocationList.get();

View File

@@ -72,7 +72,7 @@ class AuthModuleTest {
}
})
// We need to set the following fields because they are checked when
// Credential#setRefreshToken is called. However they are not actually persisted in the
// Credential#setRefreshToken is called. However, they are not actually persisted in the
// DataStore and not actually used in tests.
.setJsonFactory(new GsonFactory())
.setTransport(new NetHttpTransport())
@@ -104,7 +104,7 @@ class AuthModuleTest {
AuthModule.provideClientScopeQualifier("client-id", ImmutableList.of("foo", "bar"));
// If we change the way we encode client id and scopes, this assertion will break. That's
// probably ok and you can just change the text. The things you have to be aware of are:
// probably ok, and you can just change the text. The things you have to be aware of are:
// - Names in the new encoding should have a low risk of collision with the old encoding.
// - Changing the encoding will force all OAuth users of the nomulus tool to do a new login
// (existing credentials will not be used).
@@ -155,7 +155,7 @@ class AuthModuleTest {
AuthModule.provideClientScopeQualifier(AuthModule.provideClientId(clientSecrets), scopes));
}
private GoogleClientSecrets getSecrets() {
private static GoogleClientSecrets getSecrets() {
return new GoogleClientSecrets()
.setInstalled(
AuthModule.provideDefaultInstalledDetails()
@@ -166,7 +166,8 @@ class AuthModuleTest {
@Test
void test_provideLocalCredentialJson() {
String credentialJson =
AuthModule.provideLocalCredentialJson(this::getSecrets, this::getCredential, null);
AuthModule.provideLocalCredentialJson(
AuthModuleTest::getSecrets, this::getCredential, null);
Map<String, String> jsonMap =
new Gson().fromJson(credentialJson, new TypeToken<Map<String, String>>() {}.getType());
assertThat(jsonMap.get("type")).isEqualTo("authorized_user");
@@ -182,7 +183,7 @@ class AuthModuleTest {
Files.write(credentialFile.toPath(), "{some_field: some_value}".getBytes(UTF_8));
String credentialJson =
AuthModule.provideLocalCredentialJson(
this::getSecrets, this::getCredential, credentialFile.getCanonicalPath());
AuthModuleTest::getSecrets, this::getCredential, credentialFile.getCanonicalPath());
assertThat(credentialJson).isEqualTo("{some_field: some_value}");
}

View File

@@ -41,6 +41,7 @@ import com.google.common.io.Files;
import com.google.common.testing.TestLogHandler;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.tld.Tld;
import google.registry.model.tld.Tld.TldNotFoundException;
import google.registry.model.tld.label.PremiumList;
import google.registry.model.tld.label.PremiumListDao;
import java.io.File;
@@ -570,4 +571,23 @@ public class ConfigureTldCommandTest extends CommandTestCase<ConfigureTldCommand
"Changes can not be applied since TLD is in breakglass mode but the breakglass flag"
+ " was not used");
}
@Test
void testSuccess_dryRunOnCreate_noChanges() throws Exception {
File tldFile = tmpDir.resolve("tld.yaml").toFile();
Files.asCharSink(tldFile, UTF_8).write(loadFile(getClass(), "tld.yaml"));
runCommandForced("--input=" + tldFile, "--dryrun");
assertThrows(TldNotFoundException.class, () -> Tld.get("tld"));
}
@Test
void testSuccess_dryRunOnUpdate_noChanges() throws Exception {
Tld tld = createTld("tld");
assertThat(tld.getCreateBillingCost()).isEqualTo(Money.of(USD, 13));
File tldFile = tmpDir.resolve("tld.yaml").toFile();
Files.asCharSink(tldFile, UTF_8).write(loadFile(getClass(), "tld.yaml"));
runCommandForced("--input=" + tldFile, "-d");
Tld notUpdatedTld = Tld.get("tld");
assertThat(notUpdatedTld.getCreateBillingCost()).isEqualTo(Money.of(USD, 13));
}
}

View File

@@ -18,9 +18,6 @@ import static com.google.common.truth.Truth.assertThat;
import static google.registry.tools.RequestFactoryModule.REQUEST_TIMEOUT_MS;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import com.google.api.client.http.GenericUrl;
@@ -35,6 +32,7 @@ import com.google.auth.oauth2.UserCredentials;
import google.registry.config.RegistryConfig;
import google.registry.testing.SystemPropertyExtension;
import google.registry.util.GoogleCredentialsBundle;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -50,7 +48,6 @@ public class RequestFactoryModuleTest {
final SystemPropertyExtension systemPropertyExtension = new SystemPropertyExtension();
@Mock public GoogleCredentialsBundle credentialsBundle;
@Mock public HttpRequestInitializer httpRequestInitializer;
@BeforeEach
void beforeEach() {
@@ -64,12 +61,11 @@ public class RequestFactoryModuleTest {
RegistryConfig.CONFIG_SETTINGS.get().gcpProject.isLocal = true;
try {
HttpRequestFactory factory =
RequestFactoryModule.provideHttpRequestFactory(credentialsBundle, "client-id", false);
RequestFactoryModule.provideHttpRequestFactory(credentialsBundle, "client-id");
HttpRequestInitializer initializer = factory.getInitializer();
assertThat(initializer).isNotNull();
HttpRequest request = factory.buildGetRequest(new GenericUrl("http://localhost"));
initializer.initialize(request);
verifyNoInteractions(httpRequestInitializer);
} finally {
RegistryConfig.CONFIG_SETTINGS.get().gcpProject.isLocal = origIsLocal;
}
@@ -77,7 +73,6 @@ public class RequestFactoryModuleTest {
@Test
void test_provideHttpRequestFactory_remote() throws Exception {
when(credentialsBundle.getHttpRequestInitializer()).thenReturn(httpRequestInitializer);
// Mock the request/response to/from the OIDC server requesting an ID token
UserCredentials mockUserCredentials = mock(UserCredentials.class);
when(credentialsBundle.getGoogleCredentials()).thenReturn(mockUserCredentials);
@@ -97,23 +92,15 @@ public class RequestFactoryModuleTest {
boolean origIsLocal = RegistryConfig.CONFIG_SETTINGS.get().gcpProject.isLocal;
RegistryConfig.CONFIG_SETTINGS.get().gcpProject.isLocal = false;
try {
// With OAuth header.
HttpRequestFactory factory =
RequestFactoryModule.provideHttpRequestFactory(credentialsBundle, "clientId", true);
RequestFactoryModule.provideHttpRequestFactory(credentialsBundle, "clientId");
HttpRequest request = factory.buildGetRequest(new GenericUrl("http://localhost"));
assertThat(request.getHeaders().get("Proxy-Authorization")).isEqualTo("Bearer oidc.token");
@SuppressWarnings("unchecked")
List<String> authHeaders = (List<String>) request.getHeaders().get("Authorization");
assertThat(authHeaders.size()).isEqualTo(1);
assertThat(authHeaders.get(0)).isEqualTo("Bearer oidc.token");
assertThat(request.getConnectTimeout()).isEqualTo(REQUEST_TIMEOUT_MS);
assertThat(request.getReadTimeout()).isEqualTo(REQUEST_TIMEOUT_MS);
verify(httpRequestInitializer).initialize(request);
verifyNoMoreInteractions(httpRequestInitializer);
// No OAuth header.
factory =
RequestFactoryModule.provideHttpRequestFactory(credentialsBundle, "clientId", false);
request = factory.buildGetRequest(new GenericUrl("http://localhost"));
assertThat(request.getHeaders().get("Proxy-Authorization")).isEqualTo("Bearer oidc.token");
assertThat(request.getConnectTimeout()).isEqualTo(REQUEST_TIMEOUT_MS);
assertThat(request.getReadTimeout()).isEqualTo(REQUEST_TIMEOUT_MS);
verifyNoMoreInteractions(httpRequestInitializer);
} finally {
RegistryConfig.CONFIG_SETTINGS.get().gcpProject.isLocal = origIsLocal;
}

View File

@@ -27,7 +27,6 @@ import google.registry.model.console.UserRoles;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.request.RequestModule;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthSettings.AuthLevel;
import google.registry.request.auth.UserAuthInfo;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeResponse;
@@ -55,8 +54,7 @@ public class ConsoleDomainGetActionTest {
void testSuccess_fullJsonRepresentation() {
ConsoleDomainGetAction action =
createAction(
AuthResult.create(
AuthLevel.USER,
AuthResult.createUser(
UserAuthInfo.create(
createUser(
new UserRoles.Builder()
@@ -85,7 +83,8 @@ public class ConsoleDomainGetActionTest {
@Test
void testFailure_appAuth() {
ConsoleDomainGetAction action = createAction(AuthResult.create(AuthLevel.APP), "exists.tld");
ConsoleDomainGetAction action =
createAction(AuthResult.createApp("service@registry.example"), "exists.tld");
action.run();
assertThat(RESPONSE.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
}
@@ -94,8 +93,7 @@ public class ConsoleDomainGetActionTest {
void testFailure_wrongTypeOfUser() {
ConsoleDomainGetAction action =
createAction(
AuthResult.create(
AuthLevel.USER,
AuthResult.createUser(
UserAuthInfo.create(mock(com.google.appengine.api.users.User.class), false)),
"exists.tld");
action.run();
@@ -106,8 +104,7 @@ public class ConsoleDomainGetActionTest {
void testFailure_noAccessToRegistrar() {
ConsoleDomainGetAction action =
createAction(
AuthResult.create(
AuthLevel.USER, UserAuthInfo.create(createUser(new UserRoles.Builder().build()))),
AuthResult.createUser(UserAuthInfo.create(createUser(new UserRoles.Builder().build()))),
"exists.tld");
action.run();
assertThat(RESPONSE.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_NOT_FOUND);
@@ -117,8 +114,7 @@ public class ConsoleDomainGetActionTest {
void testFailure_nonexistentDomain() {
ConsoleDomainGetAction action =
createAction(
AuthResult.create(
AuthLevel.USER,
AuthResult.createUser(
UserAuthInfo.create(createUser(new UserRoles.Builder().setIsAdmin(true).build()))),
"nonexistent.tld");
action.run();

View File

@@ -24,7 +24,6 @@ import google.registry.model.console.UserRoles;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.request.RequestModule;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthSettings.AuthLevel;
import google.registry.request.auth.UserAuthInfo;
import google.registry.testing.FakeResponse;
import java.io.IOException;
@@ -50,21 +49,31 @@ class ConsoleUserDataActionTest {
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
.build();
ConsoleUserDataAction action =
createAction(AuthResult.create(AuthLevel.USER, UserAuthInfo.create(user)));
ConsoleUserDataAction action = createAction(AuthResult.createUser(UserAuthInfo.create(user)));
action.run();
assertThat(response.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_OK);
Map jsonObject = GSON.fromJson(response.getPayload(), Map.class);
assertThat(jsonObject)
.containsExactly("isAdmin", false, "technicalDocsUrl", "test", "globalRole", "FTE");
.containsExactly(
"isAdmin",
false,
"technicalDocsUrl",
"test",
"globalRole",
"FTE",
"productName",
"Nomulus",
"supportPhoneNumber",
"+1 (212) 867 5309",
"supportEmail",
"support@example.com");
}
@Test
void testFailure_notAConsoleUser() throws IOException {
ConsoleUserDataAction action =
createAction(
AuthResult.create(
AuthLevel.USER,
AuthResult.createUser(
UserAuthInfo.create(
new com.google.appengine.api.users.User(
"JohnDoe@theregistrar.com", "theregistrar.com"),
@@ -74,6 +83,7 @@ class ConsoleUserDataActionTest {
}
private ConsoleUserDataAction createAction(AuthResult authResult) throws IOException {
return new ConsoleUserDataAction(authResult, response, "test");
return new ConsoleUserDataAction(
authResult, response, "Nomulus", "support@example.com", "+1 (212) 867 5309", "test");
}
}

View File

@@ -38,7 +38,6 @@ import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.request.Action;
import google.registry.request.RequestModule;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthSettings.AuthLevel;
import google.registry.request.auth.UserAuthInfo;
import google.registry.testing.DeterministicStringGenerator;
import google.registry.testing.FakeResponse;
@@ -108,8 +107,7 @@ class RegistrarsActionTest {
RegistrarsAction action =
createAction(
Action.Method.GET,
AuthResult.create(
AuthLevel.USER,
AuthResult.createUser(
UserAuthInfo.create(
createUser(
new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_LEAD).build()))));
@@ -129,8 +127,7 @@ class RegistrarsActionTest {
RegistrarsAction action =
createAction(
Action.Method.GET,
AuthResult.create(
AuthLevel.USER,
AuthResult.createUser(
UserAuthInfo.create(
createUser(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build()))));
action.run();
@@ -151,8 +148,7 @@ class RegistrarsActionTest {
RegistrarsAction action =
createAction(
Action.Method.POST,
AuthResult.create(
AuthLevel.USER,
AuthResult.createUser(
UserAuthInfo.create(createUser(new UserRoles.Builder().setIsAdmin(true).build()))));
action.run();
assertThat(response.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_OK);
@@ -180,8 +176,7 @@ class RegistrarsActionTest {
RegistrarsAction action =
createAction(
Action.Method.POST,
AuthResult.create(
AuthLevel.USER,
AuthResult.createUser(
UserAuthInfo.create(
createUser(new UserRoles.Builder().setIsAdmin(true).build()))));
action.run();
@@ -200,8 +195,7 @@ class RegistrarsActionTest {
RegistrarsAction action =
createAction(
Action.Method.POST,
AuthResult.create(
AuthLevel.USER,
AuthResult.createUser(
UserAuthInfo.create(createUser(new UserRoles.Builder().setIsAdmin(true).build()))));
action.run();
assertThat(response.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_BAD_REQUEST);
@@ -215,8 +209,7 @@ class RegistrarsActionTest {
RegistrarsAction action =
createAction(
Action.Method.GET,
AuthResult.create(
AuthLevel.USER,
AuthResult.createUser(
UserAuthInfo.create(
createUser(
new UserRoles.Builder()

View File

@@ -37,7 +37,6 @@ import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.request.Action;
import google.registry.request.RequestModule;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthSettings.AuthLevel;
import google.registry.request.auth.UserAuthInfo;
import google.registry.testing.FakeResponse;
import google.registry.ui.server.registrar.RegistrarConsoleModule;
@@ -103,8 +102,7 @@ class ContactActionTest {
ContactAction action =
createAction(
Action.Method.GET,
AuthResult.create(
AuthLevel.USER,
AuthResult.createUser(
UserAuthInfo.create(
createUser(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build()))),
testRegistrar.getRegistrarId(),
@@ -121,8 +119,7 @@ class ContactActionTest {
ContactAction action =
createAction(
Action.Method.GET,
AuthResult.create(
AuthLevel.USER,
AuthResult.createUser(
UserAuthInfo.create(
createUser(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build()))),
testRegistrar.getRegistrarId(),
@@ -137,8 +134,7 @@ class ContactActionTest {
ContactAction action =
createAction(
Action.Method.POST,
AuthResult.create(
AuthLevel.USER,
AuthResult.createUser(
UserAuthInfo.create(
createUser(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build()))),
testRegistrar.getRegistrarId(),
@@ -160,8 +156,7 @@ class ContactActionTest {
ContactAction action =
createAction(
Action.Method.POST,
AuthResult.create(
AuthLevel.USER,
AuthResult.createUser(
UserAuthInfo.create(
createUser(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build()))),
testRegistrar.getRegistrarId(),
@@ -186,8 +181,7 @@ class ContactActionTest {
ContactAction action =
createAction(
Action.Method.POST,
AuthResult.create(
AuthLevel.USER,
AuthResult.createUser(
UserAuthInfo.create(
createUser(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build()))),
testRegistrar.getRegistrarId(),
@@ -208,8 +202,7 @@ class ContactActionTest {
ContactAction action =
createAction(
Action.Method.POST,
AuthResult.create(
AuthLevel.USER,
AuthResult.createUser(
UserAuthInfo.create(
createUser(
new UserRoles.Builder()

View File

@@ -35,7 +35,6 @@ import google.registry.model.registrar.Registrar;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.request.RequestModule;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthSettings.AuthLevel;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.UserAuthInfo;
import google.registry.testing.FakeClock;
@@ -92,8 +91,7 @@ class SecurityActionTest {
clock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
SecurityAction action =
createAction(
AuthResult.create(
AuthLevel.USER,
AuthResult.createUser(
UserAuthInfo.create(
createUser(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build()))),
testRegistrar.getRegistrarId());

View File

@@ -33,7 +33,6 @@ import google.registry.model.registrar.Registrar;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.request.RequestModule;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthSettings.AuthLevel;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.AuthenticatedRegistrarAccessor.Role;
import google.registry.request.auth.UserAuthInfo;
@@ -127,8 +126,7 @@ public class WhoisRegistrarFieldsActionTest {
void testFailure_noAccessToRegistrar() throws Exception {
Registrar newRegistrar = Registrar.loadByRegistrarIdCached("NewRegistrar").get();
AuthResult onlyTheRegistrar =
AuthResult.create(
AuthLevel.USER,
AuthResult.createUser(
UserAuthInfo.create(
new User.Builder()
.setEmailAddress("email@email.example")
@@ -147,8 +145,7 @@ public class WhoisRegistrarFieldsActionTest {
}
private AuthResult defaultUserAuth() {
return AuthResult.create(
AuthLevel.USER,
return AuthResult.createUser(
UserAuthInfo.create(
new User.Builder()
.setEmailAddress("email@email.example")

View File

@@ -37,7 +37,6 @@ import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.request.Action.Method;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthSettings.AuthLevel;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.UserAuthInfo;
import google.registry.security.XsrfTokenManager;
@@ -93,7 +92,7 @@ public final class ConsoleOteSetupActionTest {
ImmutableSetMultimap.of("unused", AuthenticatedRegistrarAccessor.Role.ADMIN));
action.userService = UserServiceFactory.getUserService();
action.xsrfTokenManager = new XsrfTokenManager(new FakeClock(), action.userService);
action.authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(user, false));
action.authResult = AuthResult.createUser(UserAuthInfo.create(user, false));
action.sendEmailUtils =
new SendEmailUtils(
new InternetAddress("outgoing@registry.example"),

View File

@@ -37,7 +37,6 @@ import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.request.Action.Method;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthSettings.AuthLevel;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.UserAuthInfo;
import google.registry.security.XsrfTokenManager;
@@ -93,7 +92,7 @@ final class ConsoleRegistrarCreatorActionTest {
ImmutableSetMultimap.of("unused", AuthenticatedRegistrarAccessor.Role.ADMIN));
action.userService = UserServiceFactory.getUserService();
action.xsrfTokenManager = new XsrfTokenManager(new FakeClock(), action.userService);
action.authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(user, false));
action.authResult = AuthResult.createUser(UserAuthInfo.create(user, false));
action.sendEmailUtils =
new SendEmailUtils(
new InternetAddress("outgoing@registry.example"),

View File

@@ -32,7 +32,6 @@ import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.request.Action.Method;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthSettings.AuthLevel;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.UserAuthInfo;
import google.registry.security.XsrfTokenManager;
@@ -78,7 +77,7 @@ class ConsoleUiActionTest {
action.xsrfTokenManager = new XsrfTokenManager(new FakeClock(), action.userService);
action.method = Method.GET;
action.paramClientId = Optional.empty();
action.authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(user, false));
action.authResult = AuthResult.createUser(UserAuthInfo.create(user, false));
action.analyticsConfig = ImmutableMap.of("googleAnalyticsId", "sampleId");
action.registrarAccessor =

View File

@@ -44,7 +44,6 @@ import google.registry.request.JsonActionRunner;
import google.registry.request.JsonResponse;
import google.registry.request.ResponseImpl;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthSettings.AuthLevel;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.UserAuthInfo;
import google.registry.testing.CloudTasksHelper;
@@ -113,8 +112,7 @@ public abstract class RegistrarSettingsActionTestCase {
gmailClient);
action.registrarConsoleMetrics = new RegistrarConsoleMetrics();
action.authResult =
AuthResult.create(
AuthLevel.USER,
AuthResult.createUser(
UserAuthInfo.create(new User("user@email.com", "email.com", "12345"), false));
action.certificateChecker =
new CertificateChecker(

View File

@@ -40,7 +40,6 @@ import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.request.Action.Method;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthSettings.AuthLevel;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.UserAuthInfo;
import google.registry.testing.FakeClock;
@@ -75,7 +74,7 @@ final class RegistryLockGetActionTest {
void beforeEach() {
user = userFromRegistrarPoc(makeRegistrarContact3());
fakeClock.setTo(DateTime.parse("2000-06-08T22:00:00.0Z"));
authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(user, false));
authResult = AuthResult.createUser(UserAuthInfo.create(user, false));
accessor =
AuthenticatedRegistrarAccessor.createForTesting(
ImmutableSetMultimap.of(
@@ -109,7 +108,7 @@ final class RegistryLockGetActionTest {
.build())
.build();
action.authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(consoleUser));
action.authResult = AuthResult.createUser(UserAuthInfo.create(consoleUser));
action.run();
assertThat(response.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_OK);
assertThat(GSON.fromJson(response.getPayload(), Map.class))
@@ -336,7 +335,7 @@ final class RegistryLockGetActionTest {
persistResource(makeRegistrar2().asBuilder().setRegistryLockAllowed(false).build());
// disallow the other user
persistResource(makeRegistrarContact2().asBuilder().setLoginEmailAddress(null).build());
authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(user, true));
authResult = AuthResult.createUser(UserAuthInfo.create(user, true));
accessor =
AuthenticatedRegistrarAccessor.createForTesting(
ImmutableSetMultimap.of(
@@ -364,7 +363,7 @@ final class RegistryLockGetActionTest {
void testSuccess_linkedToLoginContactEmail() {
// Note that the email address is case-insensitive.
user = new User("marla.singer@crr.com", "crr.com", user.getUserId());
authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(user, false));
authResult = AuthResult.createUser(UserAuthInfo.create(user, false));
action =
new RegistryLockGetAction(
Method.GET, response, accessor, authResult, Optional.of("TheRegistrar"));

View File

@@ -45,7 +45,6 @@ import google.registry.request.JsonActionRunner;
import google.registry.request.JsonResponse;
import google.registry.request.ResponseImpl;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthSettings.AuthLevel;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.AuthenticatedRegistrarAccessor.Role;
import google.registry.request.auth.UserAuthInfo;
@@ -115,8 +114,7 @@ final class RegistryLockPostActionTest {
when(mockRequest.getServerName()).thenReturn("registrarconsole.tld");
action =
createAction(
AuthResult.create(AuthLevel.USER, UserAuthInfo.create(userWithLockPermission, false)));
createAction(AuthResult.createUser(UserAuthInfo.create(userWithLockPermission, false)));
}
@Test
@@ -154,9 +152,7 @@ final class RegistryLockPostActionTest {
saveRegistryLock(
createLock().asBuilder().isSuperuser(true).setLockCompletionTime(clock.nowUtc()).build());
persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
action =
createAction(
AuthResult.create(AuthLevel.USER, UserAuthInfo.create(userWithoutPermission, true)));
action = createAction(AuthResult.createUser(UserAuthInfo.create(userWithoutPermission, true)));
Map<String, ?> response = action.handleJsonRequest(unlockRequest());
// we should still email the admin user's email address
assertSuccess(response, "unlock", "johndoe@theregistrar.com");
@@ -166,8 +162,7 @@ final class RegistryLockPostActionTest {
void testSuccess_linkedToLoginEmail() throws Exception {
userWithLockPermission = new User("Marla.Singer@crr.com", "crr.com");
action =
createAction(
AuthResult.create(AuthLevel.USER, UserAuthInfo.create(userWithLockPermission, false)));
createAction(AuthResult.createUser(UserAuthInfo.create(userWithLockPermission, false)));
Map<String, ?> response = action.handleJsonRequest(lockRequest());
assertSuccess(response, "lock", "Marla.Singer.RegistryLock@crr.com");
}
@@ -205,18 +200,14 @@ final class RegistryLockPostActionTest {
@Test
void testSuccess_adminUser() throws Exception {
// Admin user should be able to lock/unlock regardless -- and we use the admin user's email
action =
createAction(
AuthResult.create(AuthLevel.USER, UserAuthInfo.create(userWithoutPermission, true)));
action = createAction(AuthResult.createUser(UserAuthInfo.create(userWithoutPermission, true)));
Map<String, ?> response = action.handleJsonRequest(lockRequest());
assertSuccess(response, "lock", "johndoe@theregistrar.com");
}
@Test
void testSuccess_adminUser_doesNotRequirePassword() throws Exception {
action =
createAction(
AuthResult.create(AuthLevel.USER, UserAuthInfo.create(userWithoutPermission, true)));
action = createAction(AuthResult.createUser(UserAuthInfo.create(userWithoutPermission, true)));
Map<String, ?> response =
action.handleJsonRequest(
ImmutableMap.of(
@@ -239,8 +230,7 @@ final class RegistryLockPostActionTest {
.build())
.setRegistryLockPassword("hi")
.build();
AuthResult consoleAuthResult =
AuthResult.create(AuthLevel.USER, UserAuthInfo.create(consoleUser));
AuthResult consoleAuthResult = AuthResult.createUser(UserAuthInfo.create(consoleUser));
action = createAction(consoleAuthResult);
Map<String, ?> response = action.handleJsonRequest(lockRequest());
assertSuccess(response, "lock", "johndoe@theregistrar.com");
@@ -253,8 +243,7 @@ final class RegistryLockPostActionTest {
.setEmailAddress("johndoe@theregistrar.com")
.setUserRoles(new UserRoles.Builder().setIsAdmin(true).build())
.build();
AuthResult consoleAuthResult =
AuthResult.create(AuthLevel.USER, UserAuthInfo.create(consoleUser));
AuthResult consoleAuthResult = AuthResult.createUser(UserAuthInfo.create(consoleUser));
action = createAction(consoleAuthResult);
Map<String, Object> requestMapWithoutPassword =
ImmutableMap.of(
@@ -286,7 +275,7 @@ final class RegistryLockPostActionTest {
@Test
void testFailure_unauthorizedRegistrarId() {
AuthResult authResult =
AuthResult.create(AuthLevel.USER, UserAuthInfo.create(userWithLockPermission, false));
AuthResult.createUser(UserAuthInfo.create(userWithLockPermission, false));
action = createAction(authResult, ImmutableSet.of("TheRegistrar"));
Map<String, ?> response =
action.handleJsonRequest(
@@ -358,9 +347,7 @@ final class RegistryLockPostActionTest {
@Test
void testFailure_notEnabledForRegistrarPoc() {
action =
createAction(
AuthResult.create(AuthLevel.USER, UserAuthInfo.create(userWithoutPermission, false)));
action = createAction(AuthResult.createUser(UserAuthInfo.create(userWithoutPermission, false)));
Map<String, ?> response =
action.handleJsonRequest(
ImmutableMap.of(
@@ -453,8 +440,7 @@ final class RegistryLockPostActionTest {
.build())
.setRegistryLockPassword("hi")
.build();
AuthResult consoleAuthResult =
AuthResult.create(AuthLevel.USER, UserAuthInfo.create(consoleUser));
AuthResult consoleAuthResult = AuthResult.createUser(UserAuthInfo.create(consoleUser));
action = createAction(consoleAuthResult);
Map<String, ?> response =
action.handleJsonRequest(

View File

@@ -43,7 +43,6 @@ import google.registry.model.tld.Tld;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthSettings.AuthLevel;
import google.registry.request.auth.UserAuthInfo;
import google.registry.security.XsrfTokenManager;
import google.registry.testing.CloudTasksHelper;
@@ -132,7 +131,7 @@ final class RegistryLockVerifyActionTest {
@Test
void testSuccess_adminLock_createsOnlyHistoryEntry() {
action.authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(user, true));
action.authResult = AuthResult.createUser(UserAuthInfo.create(user, true));
saveRegistryLock(createLock().asBuilder().isSuperuser(true).build());
action.run();
@@ -332,7 +331,7 @@ final class RegistryLockVerifyActionTest {
stringGenerator, "adminreg", cloudTasksHelper.getTestCloudTasksUtils()),
lockVerificationCode,
isLock);
authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(user, false));
authResult = AuthResult.createUser(UserAuthInfo.create(user, false));
action.req = request;
action.response = response;
action.authResult = authResult;

View File

@@ -6,7 +6,7 @@ PATH CLASS
/_dr/task/deleteExpiredDomains DeleteExpiredDomainsAction GET n API APP ADMIN
/_dr/task/deleteLoadTestData DeleteLoadTestDataAction POST n API APP ADMIN
/_dr/task/deleteProberData DeleteProberDataAction POST n API APP ADMIN
/_dr/task/executeCannedScript CannedScriptExecutionAction POST y API APP ADMIN
/_dr/task/executeCannedScript CannedScriptExecutionAction POST,GET y API APP ADMIN
/_dr/task/expandBillingRecurrences ExpandBillingRecurrencesAction GET n API APP ADMIN
/_dr/task/exportDomainLists ExportDomainListsAction POST n API APP ADMIN
/_dr/task/exportPremiumTerms ExportPremiumTermsAction POST n API APP ADMIN

View File

@@ -1,5 +1,5 @@
PATH CLASS METHODS OK AUTH_METHODS MIN USER_POLICY
/_dr/epp EppTlsAction POST n API APP PUBLIC
/_dr/epp EppTlsAction POST n API APP ADMIN
/console-api/domain ConsoleDomainGetAction GET n API,LEGACY USER PUBLIC
/console-api/registrars RegistrarsAction GET,POST n API,LEGACY USER PUBLIC
/console-api/settings/contacts ContactAction GET,POST n API,LEGACY USER PUBLIC
@@ -13,4 +13,4 @@ PATH CLASS METHODS OK AUT
/registrar-settings RegistrarSettingsAction POST n API,LEGACY USER PUBLIC
/registry-lock-get RegistryLockGetAction GET n API,LEGACY USER PUBLIC
/registry-lock-post RegistryLockPostAction POST n API,LEGACY USER PUBLIC
/registry-lock-verify RegistryLockVerifyAction GET n API,LEGACY NONE PUBLIC
/registry-lock-verify RegistryLockVerifyAction GET n API,LEGACY NONE PUBLIC

View File

@@ -1,5 +1,5 @@
PATH CLASS METHODS OK AUTH_METHODS MIN USER_POLICY
/_dr/whois WhoisAction POST n API APP PUBLIC
/_dr/whois WhoisAction POST n API APP ADMIN
/check CheckApiAction GET n API,LEGACY NONE PUBLIC
/rdap/autnum/(*) RdapAutnumAction GET,HEAD n API,LEGACY NONE PUBLIC
/rdap/domain/(*) RdapDomainAction GET,HEAD n API,LEGACY NONE PUBLIC

View File

@@ -261,20 +261,20 @@ td.section {
</tr>
<tr>
<td class="property_name">generated on</td>
<td class="property_value">2023-07-07 17:30:23.191251</td>
<td class="property_value">2023-10-04 16:08:55.700762</td>
</tr>
<tr>
<td class="property_name">last flyway file</td>
<td id="lastFlywayFile" class="property_value">V146__last_update_time_via_epp.sql</td>
<td id="lastFlywayFile" class="property_value">V147__drop_gaia_id_from_user.sql</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p>&nbsp;</p>
<svg viewbox="0.00 0.00 4029.00 3160.50" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="erDiagram" style="overflow: hidden; width: 100%; height: 800px">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 3156.5)">
<svg viewbox="0.00 0.00 4029.00 3141.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="erDiagram" style="overflow: hidden; width: 100%; height: 800px">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 3137)">
<title>SchemaCrawler_Diagram</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-3156.5 4025,-3156.5 4025,4 -4,4" />
<polygon fill="white" stroke="transparent" points="-4,4 -4,-3137 4025,-3137 4025,4 -4,4" />
<text text-anchor="start" x="3752.5" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">
generated by
</text>
@@ -285,7 +285,7 @@ td.section {
generated on
</text>
<text text-anchor="start" x="3835.5" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">
2023-07-07 17:30:23.191251
2023-10-04 16:08:55.700762
</text>
<polygon fill="none" stroke="#888888" points="3748,-4 3748,-44 4013,-44 4013,-4 3748,-4" /> <!-- allocationtoken_a08ccbef -->
<g id="node1" class="node">
@@ -3191,44 +3191,36 @@ td.section {
</g> <!-- user_f2216f01 -->
<g id="node39" class="node">
<title>user_f2216f01</title>
<polygon fill="#ebcef2" stroke="transparent" points="3720.5,-3128 3720.5,-3147 3821.5,-3147 3821.5,-3128 3720.5,-3128" />
<text text-anchor="start" x="3722.5" y="-3134.8" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">
<polygon fill="#ebcef2" stroke="transparent" points="3720.5,-3109 3720.5,-3128 3821.5,-3128 3821.5,-3109 3720.5,-3109" />
<text text-anchor="start" x="3722.5" y="-3115.8" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">
public."User"
</text>
<polygon fill="#ebcef2" stroke="transparent" points="3821.5,-3128 3821.5,-3147 3931.5,-3147 3931.5,-3128 3821.5,-3128" />
<text text-anchor="start" x="3892.5" y="-3133.8" font-family="Helvetica,sans-Serif" font-size="14.00">
<polygon fill="#ebcef2" stroke="transparent" points="3821.5,-3109 3821.5,-3128 3931.5,-3128 3931.5,-3109 3821.5,-3109" />
<text text-anchor="start" x="3892.5" y="-3114.8" font-family="Helvetica,sans-Serif" font-size="14.00">
[table]
</text>
<text text-anchor="start" x="3722.5" y="-3115.8" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">
<text text-anchor="start" x="3722.5" y="-3096.8" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">
id
</text>
<text text-anchor="start" x="3815.5" y="-3114.8" font-family="Helvetica,sans-Serif" font-size="14.00">
</text>
<text text-anchor="start" x="3823.5" y="-3114.8" font-family="Helvetica,sans-Serif" font-size="14.00">
bigserial not null
</text>
<text text-anchor="start" x="3815.5" y="-3095.8" font-family="Helvetica,sans-Serif" font-size="14.00">
</text>
<text text-anchor="start" x="3823.5" y="-3095.8" font-family="Helvetica,sans-Serif" font-size="14.00">
auto-incremented
</text>
<text text-anchor="start" x="3722.5" y="-3076.8" font-family="Helvetica,sans-Serif" font-size="14.00">
email_address
bigserial not null
</text>
<text text-anchor="start" x="3815.5" y="-3076.8" font-family="Helvetica,sans-Serif" font-size="14.00">
</text>
<text text-anchor="start" x="3823.5" y="-3076.8" font-family="Helvetica,sans-Serif" font-size="14.00">
text not null
auto-incremented
</text>
<text text-anchor="start" x="3722.5" y="-3057.8" font-family="Helvetica,sans-Serif" font-size="14.00">
gaia_id
email_address
</text>
<text text-anchor="start" x="3815.5" y="-3057.8" font-family="Helvetica,sans-Serif" font-size="14.00">
</text>
<text text-anchor="start" x="3823.5" y="-3057.8" font-family="Helvetica,sans-Serif" font-size="14.00">
text
text not null
</text>
<polygon fill="none" stroke="#888888" points="3719,-3051.5 3719,-3148.5 3932,-3148.5 3932,-3051.5 3719,-3051.5" />
<polygon fill="none" stroke="#888888" points="3719,-3051 3719,-3129 3932,-3129 3932,-3051 3719,-3051" />
</g>
</g>
</svg>
@@ -7114,11 +7106,6 @@ td.section {
<td class="minwidth">email_address</td>
<td class="minwidth">text not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth">gaia_id</td>
<td class="minwidth">text</td>
</tr>
<tr>
<td colspan="3"></td>
</tr>

File diff suppressed because it is too large Load Diff

View File

@@ -144,3 +144,4 @@ V143__idn_per_tld.sql
V144__drop_database_migration_state_schedule_table.sql
V145__add_breakglass_mode_to_tld_table.sql
V146__last_update_time_via_epp.sql
V147__drop_gaia_id_from_user.sql

View File

@@ -0,0 +1,16 @@
-- Copyright 2023 The Nomulus Authors. All Rights Reserved.
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
ALTER TABLE "User" DROP COLUMN gaia_id;

View File

@@ -1115,7 +1115,6 @@ CREATE TABLE public."TmchCrl" (
CREATE TABLE public."User" (
id bigint NOT NULL,
email_address text NOT NULL,
gaia_id text,
registry_lock_password_hash text,
registry_lock_password_salt text,
global_role text NOT NULL,
@@ -2135,13 +2134,6 @@ CREATE INDEX spec11threatmatch_tld_idx ON public."Spec11ThreatMatch" USING btree
CREATE INDEX user_email_address_idx ON public."User" USING btree (email_address);
--
-- Name: user_gaia_id_idx; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX user_gaia_id_idx ON public."User" USING btree (gaia_id);
--
-- Name: Contact fk1sfyj7o7954prbn1exk7lpnoe; Type: FK CONSTRAINT; Schema: public; Owner: -
--

View File

@@ -88,8 +88,7 @@ gSuite:
For fully-featured production environments that need the full range of features
(e.g. RDE, correct contact information on the registrar console, etc.) you will
need to specify more settings. The `nomulus-config-production-sample.yaml` file
contains an exhaustive list of all settings to override.
need to specify more settings.
From a code perspective, all configuration settings ultimately come through the
[`RegistryConfig`][registry-config] class. This includes a Dagger module called

View File

@@ -134,16 +134,16 @@ takes a couple of minutes.
### Setup Nomulus
After terraform completes, it outputs some information, among which is the
client id of the service account created for the proxy. This needs to be added
to the Nomulus configuration file so that Nomulus accepts traffic from the
email address of the service account created for the proxy. This needs to be
added to the Nomulus configuration file so that Nomulus accepts traffic from the
proxy. Edit the following section in
`java/google/registry/config/files/nomulus-config-<env>.yaml` and redeploy
Nomulus:
```yaml
oAuth:
allowedOauthClientIds:
- <client_id>
auth:
allowedServiceAccountEmails:
- <email address>
```
### Setup nameservers
@@ -304,15 +304,15 @@ $ gcloud iam service-accounts keys create proxy-key.json --iam-account \
A `proxy-key.json` file will be created inside the current working directory.
The `client_id` inside the key file needs to be added to the Nomulus
The service account email address needs to be added to the Nomulus
configuration file so that Nomulus accepts the OAuth tokens generated for this
service account. Add its value to
`java/google/registry/config/files/nomulus-config-<env>.yaml`:
```yaml
oAuth:
allowedOauthClientIds:
- <client_id>
auth:
allowedServiceAccountEmails:
- <email address>
```
Redeploy Nomulus for the change to take effect.

View File

@@ -91,6 +91,35 @@ steps:
--format="get(digest)" --filter="tags = ${TAG_NAME}")
sed -i s/'prober_cert_updater:latest'/prober_cert_updater@$digest/g \
release/cloudbuild-renew-prober-certs-*.yaml
# Build the tld_updater image and upload it to GCR. This image extends
# from the `builder` and the nomulus.jar built earlier.
- name: 'gcr.io/cloud-builders/docker'
entrypoint: /bin/bash
args:
- -c
- |
set -e
# The nomulus jar is not under the working dir. Must be copied over.
cp ../../output/nomulus.jar .
docker build -t gcr.io/${PROJECT_ID}/tld_updater:${TAG_NAME} \
--build-arg TAG_NAME=${TAG_NAME} --build-arg PROJECT_ID=${PROJECT_ID} .
docker tag gcr.io/${PROJECT_ID}/tld_updater:${TAG_NAME} \
gcr.io/${PROJECT_ID}/tld_updater:latest
docker push gcr.io/${PROJECT_ID}/tld_updater:latest
docker push gcr.io/${PROJECT_ID}/tld_updater:${TAG_NAME}
dir: 'release/tld-updater/'
# Update the tld_updater image digest in relevant GCB files.
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
entrypoint: /bin/bash
args:
- -c
- |
set -e
digest=$(gcloud container images list-tags \
gcr.io/${PROJECT_ID}/tld_updater \
--format="get(digest)" --filter="tags = ${TAG_NAME}")
sed -i s/'tld_updater:latest'/tld_updater@$digest/g \
release/cloudbuild-tld-sync-*.yaml
# Build and stage Dataflow Flex templates.
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
entrypoint: /bin/bash
@@ -161,6 +190,7 @@ artifacts:
- 'release/cloudbuild-renew-prober-certs-*.yaml'
- 'release/cloudbuild-schema-deploy-*.yaml'
- 'release/cloudbuild-schema-verify-*.yaml'
- 'release/cloudbuild-tld-sync-*.yaml'
timeout: 7200s
options:

View File

@@ -139,12 +139,18 @@ steps:
gcloud container images list-tags \
gcr.io/${PROJECT_ID}/prober_cert_updater \
--format='get(digest)' --filter='tags = ${TAG_NAME}')
tld_updater_digest=$( \
gcloud container images list-tags \
gcr.io/${PROJECT_ID}/tld_updater \
--format='get(digest)' --filter='tags = ${TAG_NAME}')
sed -i s/builder:latest/builder@$builder_digest/g \
release/cloudbuild-schema-deploy.yaml
sed -i s/builder:latest/builder@$builder_digest/g \
release/cloudbuild-schema-verify.yaml
sed -i s/builder:latest/builder@$builder_digest/g \
release/cloudbuild-renew-prober-certs.yaml
sed -i s/builder:latest/builder@$builder_digest/g \
release/cloudbuild-tld-sync.yaml
sed -i s/schema_deployer:latest/schema_deployer@$schema_deployer_digest/g \
release/cloudbuild-schema-deploy.yaml
sed -i s/schema_verifier:latest/schema_verifier@$schema_verifier_digest/g \
@@ -157,6 +163,8 @@ steps:
> release/cloudbuild-schema-verify-${environment}.yaml
sed s/'$${_ENV}'/${environment}/g release/cloudbuild-renew-prober-certs.yaml \
> release/cloudbuild-renew-prober-certs-${environment}.yaml
sed s/'$${_ENV}'/${environment}/g release/cloudbuild-tld-sync.yaml \
> release/cloudbuild-tld-sync-${environment}.yaml
done
# Upload the gradle binary to GCS if it does not exist and point URL in gradle wrapper to it.
- name: 'gcr.io/cloud-builders/gsutil'

View File

@@ -0,0 +1,45 @@
# This will sync the Tld configurations in the internal repo with the Tld objects in the database.
#
# To manually trigger a build on GCB, run:
# gcloud builds submit --config cloudbuild-tld-sync.yaml --substitutions \
# _INTERNAL_REPO_URL=[URL] ..
#
# To trigger a build automatically, follow the instructions below and add a trigger:
# https://cloud.google.com/cloud-build/docs/running-builds/automate-builds
#
# Note that the release process hardens the tags and variables in this file:
# - The 'latest' tag on docker images will be replaced by their image digests.
# - The ${_ENV} pattern will be replaced by the actual environment name.
# Please refer to ./cloudbuild-release.yaml for more details.
#
steps:
# Check out the internal repo.
- name: 'gcr.io/cloud-builders/git'
entrypoint: /bin/bash
args:
- -c
- |
set -e
git clone https://gerrit.googlesource.com/gcompute-tools
./gcompute-tools/git-cookie-authdaemon
git clone ${_INTERNAL_REPO_URL} nomulus-internal
# Download and decrypt the nomulus tool credential
- name: 'gcr.io/$PROJECT_ID/builder:latest'
entrypoint: /bin/bash
args:
- -c
- |
set -e
gcloud secrets versions access latest \
--secret nomulus-tool-cloudbuild-credential \
> nomulus_tool_credential.json
# Configure the TLDs using the stored configuration files in the internal repo
- name: 'gcr.io/$PROJECT_ID/tld_updater:latest'
args:
- ${_ENV}
- ./nomulus_tool_credential.json
- nomulus-internal/core/src/main/java/google/registry/config/files/tld/
timeout: 7200s
options:
machineType: 'E2_HIGHCPU_32'

View File

@@ -0,0 +1,22 @@
# Copyright 2023 The Nomulus Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
ARG PROJECT_ID
ARG TAG_NAME
FROM gcr.io/${PROJECT_ID}/builder:${TAG_NAME}
COPY nomulus.jar /
COPY sync_tlds.sh /usr/local/bin
ENTRYPOINT [ "bash", "sync_tlds.sh" ]

Some files were not shown because too many files have changed in this diff Show More