mirror of
https://github.com/google/nomulus.git
synced 2025-05-19 18:59:35 +02:00
mv com/google/domain/registry google/registry
This change renames directories in preparation for the great package rename. The repository is now in a broken state because the code itself hasn't been updated. However this should ensure that git correctly preserves history for each file.
This commit is contained in:
parent
a41677aea1
commit
5012893c1d
2396 changed files with 0 additions and 0 deletions
30
java/google/registry/ui/server/BUILD
Normal file
30
java/google/registry/ui/server/BUILD
Normal file
|
@ -0,0 +1,30 @@
|
|||
package(default_visibility = ["//java/com/google/domain/registry:registry_project"])
|
||||
|
||||
|
||||
java_library(
|
||||
name = "server",
|
||||
srcs = glob(["*.java"]),
|
||||
resources = [
|
||||
"//java/com/google/domain/registry/ui:globals.txt",
|
||||
"//java/com/google/domain/registry/ui/css:registrar_bin.css.js",
|
||||
"//java/com/google/domain/registry/ui/css:registrar_dbg.css.js",
|
||||
],
|
||||
deps = [
|
||||
"//java/com/google/common/base",
|
||||
"//java/com/google/common/collect",
|
||||
"//java/com/google/common/io",
|
||||
"//java/com/google/common/net",
|
||||
"//java/com/google/domain/registry/model",
|
||||
"//java/com/google/domain/registry/security",
|
||||
"//java/com/google/domain/registry/ui",
|
||||
"//java/com/google/domain/registry/ui/forms",
|
||||
"//java/com/google/domain/registry/util",
|
||||
"//third_party/java/appengine:appengine-api",
|
||||
"//third_party/java/joda_time",
|
||||
"//third_party/java/json_simple",
|
||||
"//third_party/java/jsr305_annotations",
|
||||
"//third_party/java/servlet/servlet_api",
|
||||
|
||||
"//third_party/closure/templates",
|
||||
],
|
||||
)
|
373
java/google/registry/ui/server/RegistrarFormFields.java
Normal file
373
java/google/registry/ui/server/RegistrarFormFields.java
Normal file
|
@ -0,0 +1,373 @@
|
|||
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.ui.server;
|
||||
|
||||
import static com.google.common.collect.Range.atLeast;
|
||||
import static com.google.common.collect.Range.atMost;
|
||||
import static com.google.common.collect.Range.closed;
|
||||
import static com.google.domain.registry.util.DomainNameUtils.canonicalizeDomainName;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import com.google.domain.registry.model.registrar.Registrar;
|
||||
import com.google.domain.registry.model.registrar.RegistrarAddress;
|
||||
import com.google.domain.registry.model.registrar.RegistrarContact;
|
||||
import com.google.domain.registry.ui.forms.FormField;
|
||||
import com.google.domain.registry.ui.forms.FormFieldException;
|
||||
import com.google.domain.registry.ui.forms.FormFields;
|
||||
import com.google.domain.registry.util.CidrAddressBlock;
|
||||
import com.google.domain.registry.util.X509Utils;
|
||||
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** Form fields for validating input for the {@code Registrar} class. */
|
||||
public final class RegistrarFormFields {
|
||||
|
||||
public static final Pattern BASE64_PATTERN = Pattern.compile("[+/a-zA-Z0-9]*");
|
||||
public static final Pattern ASCII_PATTERN = Pattern.compile("\\p{ASCII}*");
|
||||
public static final String ASCII_ERROR = "Please only use ASCII-US characters.";
|
||||
|
||||
private static final Function<String, CidrAddressBlock> CIDR_TRANSFORM =
|
||||
new Function<String, CidrAddressBlock>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public CidrAddressBlock apply(@Nullable String input) {
|
||||
try {
|
||||
return input != null ? CidrAddressBlock.create(input) : null;
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new FormFieldException("Not a valid CIDR notation IP-address block.", e);
|
||||
}
|
||||
}};
|
||||
|
||||
private static final Function<String, String> HOSTNAME_TRANSFORM =
|
||||
new Function<String, String>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public String apply(@Nullable String input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
if (!InternetDomainName.isValid(input)) {
|
||||
throw new FormFieldException("Not a valid hostname.");
|
||||
}
|
||||
return canonicalizeDomainName(input);
|
||||
}};
|
||||
|
||||
public static final FormField<String, String> NAME_FIELD =
|
||||
FormFields.NAME.asBuilderNamed("registrarName")
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> EMAIL_ADDRESS_FIELD =
|
||||
FormFields.EMAIL.asBuilderNamed("emailAddress")
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> ICANN_REFERRAL_EMAIL_FIELD =
|
||||
FormFields.EMAIL.asBuilderNamed("icannReferralEmail")
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> PHONE_NUMBER_FIELD =
|
||||
FormFields.PHONE_NUMBER.asBuilder()
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> FAX_NUMBER_FIELD =
|
||||
FormFields.PHONE_NUMBER.asBuilderNamed("faxNumber")
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.build();
|
||||
|
||||
public static final FormField<String, Registrar.State> STATE_FIELD =
|
||||
FormField.named("state")
|
||||
.emptyToNull()
|
||||
.asEnum(Registrar.State.class)
|
||||
.build();
|
||||
|
||||
public static final FormField<List<String>, Set<String>> ALLOWED_TLDS_FIELD =
|
||||
FormFields.LABEL.asBuilderNamed("allowedTlds")
|
||||
.asSet()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> WHOIS_SERVER_FIELD =
|
||||
FormFields.LABEL.asBuilderNamed("whoisServer")
|
||||
.transform(HOSTNAME_TRANSFORM)
|
||||
.build();
|
||||
|
||||
public static final FormField<Boolean, Boolean> BLOCK_PREMIUM_NAMES_FIELD =
|
||||
FormField.named("blockPremiumNames", Boolean.class)
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> DRIVE_FOLDER_ID_FIELD =
|
||||
FormFields.XS_NORMALIZED_STRING.asBuilderNamed("driveFolderId")
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> CLIENT_CERTIFICATE_HASH_FIELD =
|
||||
FormField.named("clientCertificateHash")
|
||||
.emptyToNull()
|
||||
.matches(BASE64_PATTERN, "Field must contain a base64 value.")
|
||||
.range(closed(1, 255))
|
||||
.build();
|
||||
|
||||
private static final FormField<String, String> X509_PEM_CERTIFICATE =
|
||||
FormField.named("certificate")
|
||||
.emptyToNull()
|
||||
.transform(new Function<String, String>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public String apply(@Nullable String input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
X509Utils.loadCertificate(input);
|
||||
} catch (CertificateParsingException e) {
|
||||
throw new FormFieldException("Invalid X.509 PEM certificate");
|
||||
}
|
||||
return input;
|
||||
}})
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> CLIENT_CERTIFICATE_FIELD =
|
||||
X509_PEM_CERTIFICATE.asBuilderNamed("clientCertificate").build();
|
||||
|
||||
public static final FormField<String, String> FAILOVER_CLIENT_CERTIFICATE_FIELD =
|
||||
X509_PEM_CERTIFICATE.asBuilderNamed("failoverClientCertificate").build();
|
||||
|
||||
public static final FormField<Long, Long> BILLING_IDENTIFIER_FIELD =
|
||||
FormField.named("billingIdentifier", Long.class)
|
||||
.range(atLeast(1))
|
||||
.build();
|
||||
|
||||
public static final FormField<Long, Long> IANA_IDENTIFIER_FIELD =
|
||||
FormField.named("ianaIdentifier", Long.class)
|
||||
.range(atLeast(1))
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> URL_FIELD =
|
||||
FormFields.MIN_TOKEN.asBuilderNamed("url")
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> REFERRAL_URL_FIELD =
|
||||
FormFields.MIN_TOKEN.asBuilderNamed("referralUrl")
|
||||
.build();
|
||||
|
||||
public static final FormField<List<String>, List<CidrAddressBlock>> IP_ADDRESS_WHITELIST_FIELD =
|
||||
FormField.named("ipAddressWhitelist")
|
||||
.emptyToNull()
|
||||
.transform(CidrAddressBlock.class, CIDR_TRANSFORM)
|
||||
.asList()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> PASSWORD_FIELD =
|
||||
FormFields.PASSWORD.asBuilderNamed("password")
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> PHONE_PASSCODE_FIELD =
|
||||
FormField.named("phonePasscode")
|
||||
.emptyToNull()
|
||||
.matches(Registrar.PHONE_PASSCODE_PATTERN)
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> CONTACT_NAME_FIELD =
|
||||
FormFields.NAME.asBuilderNamed("name")
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> CONTACT_EMAIL_ADDRESS_FIELD =
|
||||
FormFields.EMAIL.asBuilderNamed("emailAddress")
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<Boolean, Boolean> CONTACT_VISIBLE_IN_WHOIS_AS_ADMIN_FIELD =
|
||||
FormField.named("visibleInWhoisAsAdmin", Boolean.class)
|
||||
.build();
|
||||
|
||||
public static final FormField<Boolean, Boolean> CONTACT_VISIBLE_IN_WHOIS_AS_TECH_FIELD =
|
||||
FormField.named("visibleInWhoisAsTech", Boolean.class)
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> CONTACT_PHONE_NUMBER_FIELD =
|
||||
FormFields.PHONE_NUMBER.asBuilder()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> CONTACT_FAX_NUMBER_FIELD =
|
||||
FormFields.PHONE_NUMBER.asBuilderNamed("faxNumber")
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> CONTACT_GAE_USER_ID_FIELD =
|
||||
FormFields.NAME.asBuilderNamed("gaeUserId")
|
||||
.build();
|
||||
|
||||
public static final FormField<String, Set<RegistrarContact.Type>> CONTACT_TYPES =
|
||||
FormField.named("types")
|
||||
.uppercased()
|
||||
.asEnum(RegistrarContact.Type.class)
|
||||
.asSet(Splitter.on(',').omitEmptyStrings().trimResults())
|
||||
.build();
|
||||
|
||||
public static final Function<Map<String, ?>, RegistrarContact.Builder>
|
||||
REGISTRAR_CONTACT_TRANSFORM = new Function<Map<String, ?>, RegistrarContact.Builder>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public RegistrarContact.Builder apply(@Nullable Map<String, ?> args) {
|
||||
if (args == null) {
|
||||
return null;
|
||||
}
|
||||
RegistrarContact.Builder builder = new RegistrarContact.Builder();
|
||||
for (String name : CONTACT_NAME_FIELD.extractUntyped(args).asSet()) {
|
||||
builder.setName(name);
|
||||
}
|
||||
for (String emailAddress : CONTACT_EMAIL_ADDRESS_FIELD.extractUntyped(args).asSet()) {
|
||||
builder.setEmailAddress(emailAddress);
|
||||
}
|
||||
for (Boolean visible :
|
||||
CONTACT_VISIBLE_IN_WHOIS_AS_ADMIN_FIELD.extractUntyped(args).asSet()) {
|
||||
builder.setVisibleInWhoisAsAdmin(visible);
|
||||
}
|
||||
for (Boolean visible :
|
||||
CONTACT_VISIBLE_IN_WHOIS_AS_TECH_FIELD.extractUntyped(args).asSet()) {
|
||||
builder.setVisibleInWhoisAsTech(visible);
|
||||
}
|
||||
for (String phoneNumber : CONTACT_PHONE_NUMBER_FIELD.extractUntyped(args).asSet()) {
|
||||
builder.setPhoneNumber(phoneNumber);
|
||||
}
|
||||
for (String faxNumber : CONTACT_FAX_NUMBER_FIELD.extractUntyped(args).asSet()) {
|
||||
builder.setFaxNumber(faxNumber);
|
||||
}
|
||||
for (Set<RegistrarContact.Type> types : CONTACT_TYPES.extractUntyped(args).asSet()) {
|
||||
builder.setTypes(types);
|
||||
}
|
||||
for (String gaeUserId : CONTACT_GAE_USER_ID_FIELD.extractUntyped(args).asSet()) {
|
||||
builder.setGaeUserId(gaeUserId);
|
||||
}
|
||||
return builder;
|
||||
}};
|
||||
|
||||
public static final FormField<List<Map<String, ?>>, List<RegistrarContact.Builder>>
|
||||
CONTACTS_FIELD = FormField.mapNamed("contacts")
|
||||
.transform(RegistrarContact.Builder.class, REGISTRAR_CONTACT_TRANSFORM)
|
||||
.asList()
|
||||
.build();
|
||||
|
||||
public static final FormField<List<String>, List<String>> I18N_STREET_FIELD =
|
||||
FormFields.XS_NORMALIZED_STRING.asBuilderNamed("street")
|
||||
.range(closed(1, 255))
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.asList()
|
||||
.range(closed(1, 3))
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<List<String>, List<String>> L10N_STREET_FIELD =
|
||||
FormFields.XS_NORMALIZED_STRING.asBuilderNamed("street")
|
||||
.range(closed(1, 255))
|
||||
.asList()
|
||||
.range(closed(1, 3))
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> I18N_CITY_FIELD =
|
||||
FormFields.NAME.asBuilderNamed("city")
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> L10N_CITY_FIELD =
|
||||
FormFields.NAME.asBuilderNamed("city")
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> I18N_STATE_FIELD =
|
||||
FormFields.XS_NORMALIZED_STRING.asBuilderNamed("state")
|
||||
.range(atMost(255))
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> L10N_STATE_FIELD =
|
||||
FormFields.XS_NORMALIZED_STRING.asBuilderNamed("state")
|
||||
.range(atMost(255))
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> I18N_ZIP_FIELD =
|
||||
FormFields.XS_TOKEN.asBuilderNamed("zip")
|
||||
.range(atMost(16))
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> L10N_ZIP_FIELD =
|
||||
FormFields.XS_TOKEN.asBuilderNamed("zip")
|
||||
.range(atMost(16))
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> COUNTRY_CODE_FIELD =
|
||||
FormFields.COUNTRY_CODE.asBuilder()
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<Map<String, ?>, RegistrarAddress> L10N_ADDRESS_FIELD =
|
||||
FormField.mapNamed("localizedAddress")
|
||||
.transform(RegistrarAddress.class, newAddressTransform(
|
||||
L10N_STREET_FIELD, L10N_CITY_FIELD, L10N_STATE_FIELD, L10N_ZIP_FIELD))
|
||||
.build();
|
||||
|
||||
private static Function<Map<String, ?>, RegistrarAddress> newAddressTransform(
|
||||
final FormField<List<String>, List<String>> streetField,
|
||||
final FormField<String, String> cityField,
|
||||
final FormField<String, String> stateField,
|
||||
final FormField<String, String> zipField) {
|
||||
return new Function<Map<String, ?>, RegistrarAddress>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public RegistrarAddress apply(@Nullable Map<String, ?> args) {
|
||||
if (args == null) {
|
||||
return null;
|
||||
}
|
||||
RegistrarAddress.Builder builder = new RegistrarAddress.Builder();
|
||||
String countryCode = COUNTRY_CODE_FIELD.extractUntyped(args).get();
|
||||
builder.setCountryCode(countryCode);
|
||||
for (List<String> streets : streetField.extractUntyped(args).asSet()) {
|
||||
builder.setStreet(ImmutableList.copyOf(streets));
|
||||
}
|
||||
for (String city : cityField.extractUntyped(args).asSet()) {
|
||||
builder.setCity(city);
|
||||
}
|
||||
for (String state : stateField.extractUntyped(args).asSet()) {
|
||||
if ("US".equals(countryCode)) {
|
||||
state = state.toUpperCase();
|
||||
if (!StateCode.US_MAP.containsKey(state)) {
|
||||
throw new FormFieldException(stateField, "Unknown US state code.");
|
||||
}
|
||||
}
|
||||
builder.setState(state);
|
||||
}
|
||||
for (String zip : zipField.extractUntyped(args).asSet()) {
|
||||
builder.setZip(zip);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
139
java/google/registry/ui/server/SoyTemplateUtils.java
Normal file
139
java/google/registry/ui/server/SoyTemplateUtils.java
Normal file
|
@ -0,0 +1,139 @@
|
|||
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.ui.server;
|
||||
|
||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||
import static com.google.common.base.Suppliers.memoize;
|
||||
import static com.google.common.io.Resources.asCharSource;
|
||||
import static com.google.common.io.Resources.getResource;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.io.Resources;
|
||||
import com.google.domain.registry.ui.ConsoleDebug;
|
||||
import com.google.template.soy.SoyFileSet;
|
||||
import com.google.template.soy.SoyUtils;
|
||||
import com.google.template.soy.base.SoySyntaxException;
|
||||
import com.google.template.soy.parseinfo.SoyFileInfo;
|
||||
import com.google.template.soy.shared.SoyCssRenamingMap;
|
||||
import com.google.template.soy.tofu.SoyTofu;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/** Helper methods for rendering Soy templates from Java code. */
|
||||
public final class SoyTemplateUtils {
|
||||
|
||||
/** Returns a memoized supplier containing compiled tofu. */
|
||||
public static Supplier<SoyTofu> createTofuSupplier(final SoyFileInfo... soyInfos) {
|
||||
return memoize(new Supplier<SoyTofu>() {
|
||||
@Override
|
||||
public SoyTofu get() {
|
||||
ConsoleDebug debugMode = ConsoleDebug.get();
|
||||
SoyFileSet.Builder builder = SoyFileSet.builder();
|
||||
for (SoyFileInfo soyInfo : soyInfos) {
|
||||
builder.add(getResource(soyInfo.getClass(), soyInfo.getFileName()));
|
||||
}
|
||||
Map<String, Object> globals = new HashMap<>();
|
||||
try {
|
||||
globals.putAll(SoyUtils.parseCompileTimeGlobals(asCharSource(SOY_GLOBALS, UTF_8)));
|
||||
} catch (SoySyntaxException | IOException e) {
|
||||
throw new RuntimeException("Failed to load soy globals", e);
|
||||
}
|
||||
globals.put("DEBUG", debugMode.ordinal());
|
||||
builder.setCompileTimeGlobals(globals);
|
||||
return builder.build().compileToTofu();
|
||||
}});
|
||||
}
|
||||
|
||||
/** Returns a memoized supplier of the thing you pass to {@code setCssRenamingMap()}. */
|
||||
public static Supplier<SoyCssRenamingMap> createCssRenamingMapSupplier(
|
||||
final URL cssMap,
|
||||
final URL cssMapDebug) {
|
||||
return memoize(new Supplier<SoyCssRenamingMap>() {
|
||||
@Override
|
||||
public SoyCssRenamingMap get() {
|
||||
final ImmutableMap<String, String> renames = getCssRenames(cssMap, cssMapDebug);
|
||||
return new SoyCssRenamingMap() {
|
||||
@Override
|
||||
public String get(String cssClassName) {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (String part : CSS_CLASS_SPLITTER.split(cssClassName)) {
|
||||
result.add(firstNonNull(renames.get(part), part));
|
||||
}
|
||||
return CSS_CLASS_JOINER.join(result);
|
||||
}};
|
||||
}});
|
||||
}
|
||||
|
||||
private static ImmutableMap<String, String> getCssRenames(URL cssMap, URL cssMapDebug) {
|
||||
try {
|
||||
switch (ConsoleDebug.get()) {
|
||||
case RAW:
|
||||
return ImmutableMap.of(); // See firstNonNull() above for clarification.
|
||||
case DEBUG:
|
||||
return extractCssRenames(Resources.toString(cssMapDebug, UTF_8));
|
||||
default:
|
||||
return extractCssRenames(Resources.toString(cssMap, UTF_8));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to load css map", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract class name rewrites from a {@code .css.js} mapping file.
|
||||
*
|
||||
* <p>This is the file created when you pass {@code --css_renaming_output_file} to the Closure
|
||||
* Stylesheets compiler. In order for this to work, {@code --output_renaming_map_format} should
|
||||
* be {@code CLOSURE_COMPILED} or {@code CLOSURE_UNCOMPILED}.
|
||||
*
|
||||
* <p>Here's an example of what the {@code .css.js} file looks like:<pre>
|
||||
*
|
||||
* goog.setCssNameMapping({
|
||||
* "nonLatin": "a",
|
||||
* "secondary": "b",
|
||||
* "mobile": "c"
|
||||
* });</pre>
|
||||
*
|
||||
* <p>This is a burden that's only necessary for tofu, since the closure compiler is smart enough
|
||||
* to substitute CSS class names when soy is compiled to JavaScript.
|
||||
*/
|
||||
private static ImmutableMap<String, String> extractCssRenames(String json) {
|
||||
ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<>();
|
||||
Matcher matcher = KEY_VALUE_PATTERN.matcher(json);
|
||||
while (matcher.find()) {
|
||||
builder.put(matcher.group(1), matcher.group(2));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static final URL SOY_GLOBALS = getResource("com/google/domain/registry/ui/globals.txt");
|
||||
private static final Splitter CSS_CLASS_SPLITTER = Splitter.on('-');
|
||||
private static final Joiner CSS_CLASS_JOINER = Joiner.on('-');
|
||||
private static final Pattern KEY_VALUE_PATTERN =
|
||||
Pattern.compile("['\"]([^'\"]+)['\"]: ['\"]([^'\"]+)['\"]");
|
||||
|
||||
private SoyTemplateUtils() {}
|
||||
}
|
82
java/google/registry/ui/server/StateCode.java
Normal file
82
java/google/registry/ui/server/StateCode.java
Normal file
|
@ -0,0 +1,82 @@
|
|||
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.ui.server;
|
||||
|
||||
import com.google.common.collect.ImmutableBiMap;
|
||||
|
||||
/**
|
||||
* Bimap of state codes and names for the US Regime.
|
||||
*
|
||||
* @see "http://statetable.com/"
|
||||
*/
|
||||
public final class StateCode {
|
||||
|
||||
public static final ImmutableBiMap<String, String> US_MAP =
|
||||
new ImmutableBiMap.Builder<String, String>()
|
||||
.put("AL", "Alabama")
|
||||
.put("AK", "Alaska")
|
||||
.put("AZ", "Arizona")
|
||||
.put("AR", "Arkansas")
|
||||
.put("CA", "California")
|
||||
.put("CO", "Colorado")
|
||||
.put("CT", "Connecticut")
|
||||
.put("DE", "Delaware")
|
||||
.put("FL", "Florida")
|
||||
.put("GA", "Georgia")
|
||||
.put("HI", "Hawaii")
|
||||
.put("ID", "Idaho")
|
||||
.put("IL", "Illinois")
|
||||
.put("IN", "Indiana")
|
||||
.put("IA", "Iowa")
|
||||
.put("KS", "Kansas")
|
||||
.put("KY", "Kentucky")
|
||||
.put("LA", "Louisiana")
|
||||
.put("ME", "Maine")
|
||||
.put("MD", "Maryland")
|
||||
.put("MA", "Massachusetts")
|
||||
.put("MI", "Michigan")
|
||||
.put("MN", "Minnesota")
|
||||
.put("MS", "Mississippi")
|
||||
.put("MO", "Missouri")
|
||||
.put("MT", "Montana")
|
||||
.put("NE", "Nebraska")
|
||||
.put("NV", "Nevada")
|
||||
.put("NH", "New Hampshire")
|
||||
.put("NJ", "New Jersey")
|
||||
.put("NM", "New Mexico")
|
||||
.put("NY", "New York")
|
||||
.put("NC", "North Carolina")
|
||||
.put("ND", "North Dakota")
|
||||
.put("OH", "Ohio")
|
||||
.put("OK", "Oklahoma")
|
||||
.put("OR", "Oregon")
|
||||
.put("PA", "Pennsylvania")
|
||||
.put("RI", "Rhode Island")
|
||||
.put("SC", "South Carolina")
|
||||
.put("SD", "South Dakota")
|
||||
.put("TN", "Tennessee")
|
||||
.put("TX", "Texas")
|
||||
.put("UT", "Utah")
|
||||
.put("VT", "Vermont")
|
||||
.put("VA", "Virginia")
|
||||
.put("WA", "Washington")
|
||||
.put("WV", "West Virginia")
|
||||
.put("WI", "Wisconsin")
|
||||
.put("WY", "Wyoming")
|
||||
.put("DC", "Washington DC")
|
||||
.build();
|
||||
|
||||
private StateCode() {}
|
||||
}
|
24
java/google/registry/ui/server/api/BUILD
Normal file
24
java/google/registry/ui/server/api/BUILD
Normal file
|
@ -0,0 +1,24 @@
|
|||
package(default_visibility = ["//java/com/google/domain/registry:registry_project"])
|
||||
|
||||
|
||||
java_library(
|
||||
name = "api",
|
||||
srcs = glob(["*.java"]),
|
||||
deps = [
|
||||
"//java/com/google/common/annotations",
|
||||
"//java/com/google/common/base",
|
||||
"//java/com/google/common/collect",
|
||||
"//java/com/google/common/net",
|
||||
"//java/com/google/domain/registry/config",
|
||||
"//java/com/google/domain/registry/flows",
|
||||
"//java/com/google/domain/registry/model",
|
||||
"//java/com/google/domain/registry/ui/server",
|
||||
"//java/com/google/domain/registry/ui/soy/api:soy_java_wrappers",
|
||||
"//java/com/google/domain/registry/util",
|
||||
"//third_party/java/json_simple",
|
||||
"//third_party/java/jsr305_annotations",
|
||||
"//third_party/java/servlet/servlet_api",
|
||||
|
||||
"//third_party/closure/templates",
|
||||
],
|
||||
)
|
138
java/google/registry/ui/server/api/CheckApiServlet.java
Normal file
138
java/google/registry/ui/server/api/CheckApiServlet.java
Normal file
|
@ -0,0 +1,138 @@
|
|||
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.ui.server.api;
|
||||
|
||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||
import static com.google.common.base.Strings.nullToEmpty;
|
||||
import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN;
|
||||
import static com.google.domain.registry.model.eppcommon.ProtocolDefinition.ServiceExtension.FEE_0_6;
|
||||
import static com.google.domain.registry.ui.server.SoyTemplateUtils.createTofuSupplier;
|
||||
import static com.google.domain.registry.util.DomainNameUtils.canonicalizeDomainName;
|
||||
import static com.google.domain.registry.util.DomainNameUtils.getTldFromDomainName;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.json.simple.JSONValue.toJSONString;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.net.MediaType;
|
||||
import com.google.domain.registry.config.RegistryEnvironment;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.EppXmlTransformer;
|
||||
import com.google.domain.registry.flows.FlowRunner;
|
||||
import com.google.domain.registry.flows.FlowRunner.CommitMode;
|
||||
import com.google.domain.registry.flows.FlowRunner.UserPrivileges;
|
||||
import com.google.domain.registry.flows.SessionMetadata.SessionSource;
|
||||
import com.google.domain.registry.flows.StatelessRequestSessionMetadata;
|
||||
import com.google.domain.registry.flows.domain.DomainCheckFlow;
|
||||
import com.google.domain.registry.model.domain.fee.FeeCheckResponseExtension;
|
||||
import com.google.domain.registry.model.domain.fee.FeeCheckResponseExtension.FeeCheck;
|
||||
import com.google.domain.registry.model.eppcommon.Trid;
|
||||
import com.google.domain.registry.model.eppinput.EppInput;
|
||||
import com.google.domain.registry.model.eppoutput.CheckData.DomainCheck;
|
||||
import com.google.domain.registry.model.eppoutput.CheckData.DomainCheckData;
|
||||
import com.google.domain.registry.model.eppoutput.Response;
|
||||
import com.google.domain.registry.ui.soy.api.DomainCheckFeeEppSoyInfo;
|
||||
import com.google.template.soy.tofu.SoyTofu;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* A servlet that returns availability and premium checks as json.
|
||||
* <p>
|
||||
* This servlet returns plain JSON without a safety prefix, so it's vital that the output not be
|
||||
* user controlled, lest it open an XSS vector. Do not modify this to return the domain name in the
|
||||
* response.
|
||||
*/
|
||||
public class CheckApiServlet extends HttpServlet {
|
||||
|
||||
private static final Supplier<SoyTofu> TOFU_SUPPLIER =
|
||||
createTofuSupplier(DomainCheckFeeEppSoyInfo.getInstance());
|
||||
|
||||
@Override
|
||||
public void doGet(HttpServletRequest req, HttpServletResponse rsp) throws IOException {
|
||||
Map<String, ?> response = doCheck(req.getParameter("domain"));
|
||||
rsp.setHeader("Content-Disposition", "attachment");
|
||||
rsp.setHeader("X-Content-Type-Options", "nosniff");
|
||||
rsp.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
|
||||
rsp.setContentType(MediaType.JSON_UTF_8.toString());
|
||||
rsp.getWriter().write(toJSONString(response));
|
||||
}
|
||||
|
||||
private StatelessRequestSessionMetadata sessionMetadata = new StatelessRequestSessionMetadata(
|
||||
RegistryEnvironment.get().config().getCheckApiServletRegistrarClientId(),
|
||||
false,
|
||||
false,
|
||||
ImmutableSet.of(FEE_0_6.getUri()),
|
||||
SessionSource.HTTP);
|
||||
|
||||
// TODO(rgr): add whitebox instrumentation for this?
|
||||
private Map<String, ?> doCheck(String domainString) {
|
||||
try {
|
||||
domainString = canonicalizeDomainName(nullToEmpty(domainString));
|
||||
// Validate the TLD.
|
||||
getTldFromDomainName(domainString);
|
||||
} catch (IllegalStateException | IllegalArgumentException e) {
|
||||
return fail("Must supply a valid second level domain name");
|
||||
}
|
||||
try {
|
||||
byte[] inputXmlBytes = TOFU_SUPPLIER.get()
|
||||
.newRenderer(DomainCheckFeeEppSoyInfo.DOMAINCHECKFEE)
|
||||
.setData(ImmutableMap.of("domainName", domainString))
|
||||
.render()
|
||||
.getBytes(UTF_8);
|
||||
Response response = new FlowRunner(
|
||||
DomainCheckFlow.class,
|
||||
EppXmlTransformer.<EppInput>unmarshal(inputXmlBytes),
|
||||
Trid.create(CheckApiServlet.class.getSimpleName()),
|
||||
sessionMetadata,
|
||||
inputXmlBytes,
|
||||
null)
|
||||
.run(CommitMode.LIVE, UserPrivileges.NORMAL)
|
||||
.getResponse();
|
||||
DomainCheckData checkData = (DomainCheckData) response.getResponseData().get(0);
|
||||
DomainCheck check = (DomainCheck) checkData.getChecks().get(0);
|
||||
boolean available = check.getName().getAvail();
|
||||
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<String, Object>()
|
||||
.put("status", "success")
|
||||
.put("available", available);
|
||||
if (available) {
|
||||
FeeCheckResponseExtension feeCheckResponse =
|
||||
(FeeCheckResponseExtension) response.getExtensions().get(0);
|
||||
FeeCheck feeCheck = feeCheckResponse.getChecks().get(0);
|
||||
builder.put("tier", firstNonNull(feeCheck.getFeeClass(), "standard"));
|
||||
} else {
|
||||
builder.put("reason", check.getReason());
|
||||
}
|
||||
return builder.build();
|
||||
} catch (EppException e) {
|
||||
return fail(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return fail("Invalid request");
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> fail(String reason) {
|
||||
return ImmutableMap.of(
|
||||
"status", "error",
|
||||
"reason", reason);
|
||||
}
|
||||
}
|
16
java/google/registry/ui/server/api/package-info.java
Normal file
16
java/google/registry/ui/server/api/package-info.java
Normal file
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
@javax.annotation.ParametersAreNonnullByDefault
|
||||
package com.google.domain.registry.ui.server.api;
|
16
java/google/registry/ui/server/package-info.java
Normal file
16
java/google/registry/ui/server/package-info.java
Normal file
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
@javax.annotation.ParametersAreNonnullByDefault
|
||||
package com.google.domain.registry.ui.server;
|
42
java/google/registry/ui/server/registrar/BUILD
Normal file
42
java/google/registry/ui/server/registrar/BUILD
Normal file
|
@ -0,0 +1,42 @@
|
|||
package(default_visibility = ["//java/com/google/domain/registry:registry_project"])
|
||||
|
||||
|
||||
java_library(
|
||||
name = "registrar",
|
||||
srcs = glob(["*.java"]),
|
||||
resources = [
|
||||
"//java/com/google/domain/registry/ui/css:registrar_bin.css.js",
|
||||
"//java/com/google/domain/registry/ui/css:registrar_dbg.css.js",
|
||||
],
|
||||
deps = [
|
||||
"//java/com/google/common/annotations",
|
||||
"//java/com/google/common/base",
|
||||
"//java/com/google/common/collect",
|
||||
"//java/com/google/common/io",
|
||||
"//java/com/google/common/net",
|
||||
"//java/com/google/domain/registry/braintree",
|
||||
"//java/com/google/domain/registry/config",
|
||||
"//java/com/google/domain/registry/export/sheet",
|
||||
"//java/com/google/domain/registry/flows",
|
||||
"//java/com/google/domain/registry/model",
|
||||
"//java/com/google/domain/registry/request",
|
||||
"//java/com/google/domain/registry/security",
|
||||
"//java/com/google/domain/registry/security:servlets",
|
||||
"//java/com/google/domain/registry/ui/forms",
|
||||
"//java/com/google/domain/registry/ui/server",
|
||||
"//java/com/google/domain/registry/ui/soy:soy_java_wrappers",
|
||||
"//java/com/google/domain/registry/ui/soy/registrar:soy_java_wrappers",
|
||||
"//java/com/google/domain/registry/util",
|
||||
"//third_party/java/appengine:appengine-api",
|
||||
"//third_party/java/braintree",
|
||||
"//third_party/java/dagger",
|
||||
"//third_party/java/joda_money",
|
||||
"//third_party/java/jsr305_annotations",
|
||||
"//third_party/java/jsr330_inject",
|
||||
"//third_party/java/objectify:objectify-v4_1",
|
||||
"//third_party/java/servlet/servlet_api",
|
||||
|
||||
"//third_party/closure/templates",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
107
java/google/registry/ui/server/registrar/ConsoleUiAction.java
Normal file
107
java/google/registry/ui/server/registrar/ConsoleUiAction.java
Normal file
|
@ -0,0 +1,107 @@
|
|||
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.ui.server.registrar;
|
||||
|
||||
import static com.google.common.net.HttpHeaders.X_FRAME_OPTIONS;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
|
||||
|
||||
import com.google.appengine.api.users.UserService;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.io.Resources;
|
||||
import com.google.common.net.MediaType;
|
||||
import com.google.domain.registry.config.ConfigModule.Config;
|
||||
import com.google.domain.registry.flows.EppConsoleServlet;
|
||||
import com.google.domain.registry.model.registrar.Registrar;
|
||||
import com.google.domain.registry.request.Action;
|
||||
import com.google.domain.registry.request.Response;
|
||||
import com.google.domain.registry.security.XsrfTokenManager;
|
||||
import com.google.domain.registry.ui.server.SoyTemplateUtils;
|
||||
import com.google.domain.registry.ui.soy.registrar.ConsoleSoyInfo;
|
||||
import com.google.template.soy.data.SoyMapData;
|
||||
import com.google.template.soy.shared.SoyCssRenamingMap;
|
||||
import com.google.template.soy.tofu.SoyTofu;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/** Action that serves Registrar Console single HTML page (SPA). */
|
||||
@Action(path = ConsoleUiAction.PATH, requireLogin = true, xsrfProtection = false)
|
||||
public final class ConsoleUiAction implements Runnable {
|
||||
|
||||
public static final String PATH = "/registrar";
|
||||
|
||||
private static final Supplier<SoyTofu> TOFU_SUPPLIER =
|
||||
SoyTemplateUtils.createTofuSupplier(
|
||||
com.google.domain.registry.ui.soy.ConsoleSoyInfo.getInstance(),
|
||||
com.google.domain.registry.ui.soy.registrar.ConsoleSoyInfo.getInstance());
|
||||
|
||||
@VisibleForTesting // webdriver and screenshot tests need this
|
||||
public static final Supplier<SoyCssRenamingMap> CSS_RENAMING_MAP_SUPPLIER =
|
||||
SoyTemplateUtils.createCssRenamingMapSupplier(
|
||||
Resources.getResource("com/google/domain/registry/ui/css/registrar_bin.css.js"),
|
||||
Resources.getResource("com/google/domain/registry/ui/css/registrar_dbg.css.js"));
|
||||
|
||||
@Inject HttpServletRequest req;
|
||||
@Inject Response response;
|
||||
@Inject SessionUtils sessionUtils;
|
||||
@Inject UserService userService;
|
||||
@Inject @Config("registrarConsoleEnabled") boolean enabled;
|
||||
@Inject ConsoleUiAction() {}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
response.setContentType(MediaType.HTML_UTF_8);
|
||||
response.setHeader(X_FRAME_OPTIONS, "SAMEORIGIN"); // Disallow iframing.
|
||||
response.setHeader("X-Ui-Compatible", "IE=edge"); // Ask IE not to be silly.
|
||||
if (!enabled) {
|
||||
response.setStatus(SC_SERVICE_UNAVAILABLE);
|
||||
response.setPayload(
|
||||
TOFU_SUPPLIER.get()
|
||||
.newRenderer(ConsoleSoyInfo.DISABLED)
|
||||
.setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get())
|
||||
.render());
|
||||
return;
|
||||
}
|
||||
if (!sessionUtils.checkRegistrarConsoleLogin(req)) {
|
||||
SoyMapData data = new SoyMapData();
|
||||
data.put("username", userService.getCurrentUser().getNickname());
|
||||
data.put("logoutUrl", userService.createLogoutURL(PATH));
|
||||
response.setStatus(SC_FORBIDDEN);
|
||||
response.setPayload(
|
||||
TOFU_SUPPLIER.get()
|
||||
.newRenderer(ConsoleSoyInfo.WHOAREYOU)
|
||||
.setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get())
|
||||
.setData(data)
|
||||
.render());
|
||||
return;
|
||||
}
|
||||
Registrar registrar = Registrar.loadByClientId(sessionUtils.getRegistrarClientId(req));
|
||||
SoyMapData data = new SoyMapData();
|
||||
data.put("xsrfToken", XsrfTokenManager.generateToken(EppConsoleServlet.XSRF_SCOPE));
|
||||
data.put("clientId", registrar.getClientIdentifier());
|
||||
data.put("username", userService.getCurrentUser().getNickname());
|
||||
data.put("isAdmin", userService.isUserAdmin());
|
||||
data.put("logoutUrl", userService.createLogoutURL(PATH));
|
||||
data.put("showPaymentLink", registrar.getBillingMethod() == Registrar.BillingMethod.BRAINTREE);
|
||||
response.setPayload(
|
||||
TOFU_SUPPLIER.get()
|
||||
.newRenderer(ConsoleSoyInfo.MAIN)
|
||||
.setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get())
|
||||
.setData(data)
|
||||
.render());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,332 @@
|
|||
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.ui.server.registrar;
|
||||
|
||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||
import static com.google.common.base.Strings.emptyToNull;
|
||||
import static com.google.common.base.Verify.verify;
|
||||
import static com.google.domain.registry.security.JsonResponseHelper.Status.ERROR;
|
||||
import static com.google.domain.registry.security.JsonResponseHelper.Status.SUCCESS;
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.domain.registry.config.ConfigModule.Config;
|
||||
import com.google.domain.registry.model.registrar.Registrar;
|
||||
import com.google.domain.registry.request.Action;
|
||||
import com.google.domain.registry.request.JsonActionRunner;
|
||||
import com.google.domain.registry.request.JsonActionRunner.JsonAction;
|
||||
import com.google.domain.registry.security.JsonResponseHelper;
|
||||
import com.google.domain.registry.ui.forms.FormField;
|
||||
import com.google.domain.registry.ui.forms.FormFieldException;
|
||||
import com.google.domain.registry.util.FormattingLogger;
|
||||
|
||||
import com.braintreegateway.BraintreeGateway;
|
||||
import com.braintreegateway.Result;
|
||||
import com.braintreegateway.Transaction;
|
||||
import com.braintreegateway.TransactionRequest;
|
||||
import com.braintreegateway.ValidationError;
|
||||
import com.braintreegateway.ValidationErrors;
|
||||
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.money.IllegalCurrencyException;
|
||||
import org.joda.money.Money;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Action handling submission of customer payment form.
|
||||
*
|
||||
* <h3>Request Object</h3>
|
||||
*
|
||||
* <p>The request payload is a JSON object with the following fields:
|
||||
*
|
||||
* <dl>
|
||||
* <dt>amount
|
||||
* <dd>String containing a fixed point value representing the amount of money the registrar
|
||||
* customer wishes to send the registry. This amount is arbitrary and entered manually by the
|
||||
* customer in the payment form, as there is currently no integration with the billing system.
|
||||
* <dt>currency
|
||||
* <dd>String containing a three letter ISO currency code, which is used to look up the Braintree
|
||||
* merchant account ID to which payment should be posted.
|
||||
* <dt>paymentMethodNonce
|
||||
* <dd>UUID nonce string supplied by the Braintree JS SDK representing the selected payment method.
|
||||
* </dl>
|
||||
*
|
||||
* <h3>Response Object</h3>
|
||||
*
|
||||
* <p>The response payload will be a JSON response object (as defined by {@link JsonResponseHelper})
|
||||
* which, if successful, will contain a single result object with the following fields:
|
||||
*
|
||||
* <dl>
|
||||
* <dt>id
|
||||
* <dd>String containing transaction ID returned by Braintree gateway.
|
||||
* <dt>formattedAmount
|
||||
* <dd>String containing amount paid, which can be displayed to the customer on a success page.
|
||||
* </dl>
|
||||
*
|
||||
* <p><b>Note:</b> These definitions corresponds to Closure Compiler extern
|
||||
* {@code registry.rpc.Payment} which must be updated should these definitions change.
|
||||
*
|
||||
* <h3>PCI Compliance</h3>
|
||||
*
|
||||
* <p>The request object will not contain credit card information, but rather a
|
||||
* {@code payment_method_nonce} field that's populated by the Braintree JS SDK iframe.
|
||||
*
|
||||
* @see RegistrarPaymentSetupAction
|
||||
*/
|
||||
@Action(
|
||||
path = "/registrar-payment",
|
||||
method = Action.Method.POST,
|
||||
xsrfProtection = true,
|
||||
xsrfScope = "console",
|
||||
requireLogin = true)
|
||||
public final class RegistrarPaymentAction implements Runnable, JsonAction {
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
private static final FormField<String, BigDecimal> AMOUNT_FIELD =
|
||||
FormField.named("amount")
|
||||
.trimmed()
|
||||
.emptyToNull()
|
||||
.required()
|
||||
.matches(Pattern.compile("-?\\d+(?:\\.\\d+)?"), "Invalid number.")
|
||||
.transform(BigDecimal.class, new Function<String, BigDecimal>() {
|
||||
@Override
|
||||
public BigDecimal apply(String value) {
|
||||
BigDecimal result = new BigDecimal(value);
|
||||
if (result.signum() != 1) {
|
||||
throw new FormFieldException("Must be a positive number.");
|
||||
}
|
||||
return result;
|
||||
}})
|
||||
.build();
|
||||
|
||||
private static final FormField<String, CurrencyUnit> CURRENCY_FIELD =
|
||||
FormField.named("currency")
|
||||
.trimmed()
|
||||
.emptyToNull()
|
||||
.required()
|
||||
.matches(Pattern.compile("[A-Z]{3}"), "Invalid currency code.")
|
||||
.transform(CurrencyUnit.class, new Function<String, CurrencyUnit>() {
|
||||
@Override
|
||||
public CurrencyUnit apply(String value) {
|
||||
try {
|
||||
return CurrencyUnit.of(value);
|
||||
} catch (IllegalCurrencyException ignored) {
|
||||
throw new FormFieldException("Unknown ISO currency code.");
|
||||
}
|
||||
}})
|
||||
.build();
|
||||
|
||||
private static final FormField<String, String> PAYMENT_METHOD_NONCE_FIELD =
|
||||
FormField.named("paymentMethodNonce")
|
||||
.trimmed()
|
||||
.emptyToNull()
|
||||
.required()
|
||||
.build();
|
||||
|
||||
@Inject BraintreeGateway braintreeGateway;
|
||||
@Inject JsonActionRunner jsonActionRunner;
|
||||
@Inject Registrar registrar;
|
||||
@Inject @Config("braintreeMerchantAccountIds") ImmutableMap<CurrencyUnit, String> accountIds;
|
||||
@Inject RegistrarPaymentAction() {}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
jsonActionRunner.run(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> handleJsonRequest(Map<String, ?> json) {
|
||||
logger.infofmt("Processing payment: %s", json);
|
||||
String paymentMethodNonce;
|
||||
Money amount;
|
||||
String merchantAccountId;
|
||||
try {
|
||||
paymentMethodNonce = PAYMENT_METHOD_NONCE_FIELD.extractUntyped(json).get();
|
||||
try {
|
||||
amount = Money.of(
|
||||
CURRENCY_FIELD.extractUntyped(json).get(),
|
||||
AMOUNT_FIELD.extractUntyped(json).get());
|
||||
} catch (ArithmeticException e) {
|
||||
// This happens when amount has more precision than the currency allows, e.g. $3.141.
|
||||
throw new FormFieldException(AMOUNT_FIELD.name(), e.getMessage());
|
||||
}
|
||||
merchantAccountId = accountIds.get(amount.getCurrencyUnit());
|
||||
if (merchantAccountId == null) {
|
||||
throw new FormFieldException(CURRENCY_FIELD.name(), "Unsupported currency.");
|
||||
}
|
||||
} catch (FormFieldException e) {
|
||||
logger.warning(e.toString());
|
||||
return JsonResponseHelper.createFormFieldError(e.getMessage(), e.getFieldName());
|
||||
}
|
||||
Result<Transaction> result =
|
||||
braintreeGateway.transaction().sale(
|
||||
new TransactionRequest()
|
||||
.amount(amount.getAmount())
|
||||
.paymentMethodNonce(paymentMethodNonce)
|
||||
.merchantAccountId(merchantAccountId)
|
||||
.customerId(registrar.getClientIdentifier())
|
||||
.options()
|
||||
.submitForSettlement(true)
|
||||
.done());
|
||||
if (result.isSuccess()) {
|
||||
return handleSuccessResponse(result.getTarget());
|
||||
} else if (result.getTransaction() != null) {
|
||||
Transaction transaction = result.getTransaction();
|
||||
switch (transaction.getStatus()) {
|
||||
case PROCESSOR_DECLINED:
|
||||
return handleProcessorDeclined(transaction);
|
||||
case SETTLEMENT_DECLINED:
|
||||
return handleSettlementDecline(transaction);
|
||||
case GATEWAY_REJECTED:
|
||||
return handleRejection(transaction);
|
||||
default:
|
||||
return handleMiscProcessorError(transaction);
|
||||
}
|
||||
} else {
|
||||
return handleValidationErrorResponse(result.getErrors());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a transaction success response.
|
||||
*
|
||||
* @see "https://developers.braintreepayments.com/reference/response/transaction/java#success"
|
||||
* @see "https://developers.braintreepayments.com/reference/general/statuses#transaction"
|
||||
*/
|
||||
private Map<String, Object> handleSuccessResponse(Transaction transaction) {
|
||||
// XXX: Currency scaling: https://github.com/braintree/braintree_java/issues/33
|
||||
Money amount =
|
||||
Money.of(CurrencyUnit.of(transaction.getCurrencyIsoCode()),
|
||||
transaction.getAmount().stripTrailingZeros());
|
||||
logger.infofmt("Transaction for %s via %s %s with ID: %s",
|
||||
amount,
|
||||
transaction.getPaymentInstrumentType(), // e.g. credit_card, paypal_account
|
||||
transaction.getStatus(), // e.g. SUBMITTED_FOR_SETTLEMENT
|
||||
transaction.getId());
|
||||
return JsonResponseHelper
|
||||
.create(SUCCESS, "Payment processed successfully", asList(
|
||||
ImmutableMap.of(
|
||||
"id", transaction.getId(),
|
||||
"formattedAmount", formatMoney(amount))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a processor declined response.
|
||||
*
|
||||
* <p>This happens when the customer's bank blocks the transaction.
|
||||
*
|
||||
* @see "https://developers.braintreepayments.com/reference/response/transaction/java#processor-declined"
|
||||
* @see "https://articles.braintreepayments.com/control-panel/transactions/declines"
|
||||
*/
|
||||
private Map<String, Object> handleProcessorDeclined(Transaction transaction) {
|
||||
logger.warningfmt("Processor declined: %s %s",
|
||||
transaction.getProcessorResponseCode(), transaction.getProcessorResponseText());
|
||||
return JsonResponseHelper.create(ERROR,
|
||||
"Payment declined: " + transaction.getProcessorResponseText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a settlement declined response.
|
||||
*
|
||||
* <p>This is a very rare condition that, for all intents and purposes, means the same thing as a
|
||||
* processor declined response.
|
||||
*
|
||||
* @see "https://developers.braintreepayments.com/reference/response/transaction/java#processor-settlement-declined"
|
||||
* @see "https://articles.braintreepayments.com/control-panel/transactions/declines"
|
||||
*/
|
||||
private Map<String, Object> handleSettlementDecline(Transaction transaction) {
|
||||
logger.warningfmt("Settlement declined: %s %s",
|
||||
transaction.getProcessorSettlementResponseCode(),
|
||||
transaction.getProcessorSettlementResponseText());
|
||||
return JsonResponseHelper.create(ERROR,
|
||||
"Payment declined: " + transaction.getProcessorSettlementResponseText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a gateway rejection response.
|
||||
*
|
||||
* <p>This happens when a transaction is blocked due to settings we configured ourselves in the
|
||||
* Braintree control panel.
|
||||
*
|
||||
* @see "https://developers.braintreepayments.com/reference/response/transaction/java#gateway-rejection"
|
||||
* @see "https://articles.braintreepayments.com/control-panel/transactions/gateway-rejections"
|
||||
* @see "https://articles.braintreepayments.com/guides/fraud-tools/avs-cvv"
|
||||
* @see "https://articles.braintreepayments.com/guides/fraud-tools/overview"
|
||||
*/
|
||||
private Map<String, Object> handleRejection(Transaction transaction) {
|
||||
logger.warningfmt("Gateway rejection: %s", transaction.getGatewayRejectionReason());
|
||||
switch (transaction.getGatewayRejectionReason()) {
|
||||
case DUPLICATE:
|
||||
return JsonResponseHelper.create(ERROR, "Payment rejected: Possible duplicate.");
|
||||
case AVS:
|
||||
return JsonResponseHelper.create(ERROR, "Payment rejected: Invalid address.");
|
||||
case CVV:
|
||||
return JsonResponseHelper.create(ERROR, "Payment rejected: Invalid CVV code.");
|
||||
case AVS_AND_CVV:
|
||||
return JsonResponseHelper.create(ERROR, "Payment rejected: Invalid address and CVV code.");
|
||||
case FRAUD:
|
||||
return JsonResponseHelper.create(ERROR,
|
||||
"Our merchant gateway suspects this payment of fraud. Please contact support.");
|
||||
default:
|
||||
return JsonResponseHelper.create(ERROR,
|
||||
"Payment rejected: " + transaction.getGatewayRejectionReason());
|
||||
}
|
||||
}
|
||||
|
||||
/** Handles a miscellaneous transaction processing error response. */
|
||||
private Map<String, Object> handleMiscProcessorError(Transaction transaction) {
|
||||
logger.warningfmt("Error processing transaction: %s %s %s",
|
||||
transaction.getStatus(),
|
||||
transaction.getProcessorResponseCode(),
|
||||
transaction.getProcessorResponseText());
|
||||
return JsonResponseHelper.create(ERROR,
|
||||
"Payment failure: "
|
||||
+ firstNonNull(
|
||||
emptyToNull(transaction.getProcessorResponseText()),
|
||||
transaction.getStatus().toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a validation error response from Braintree.
|
||||
*
|
||||
* @see "https://developers.braintreepayments.com/reference/response/transaction/java#validation-errors"
|
||||
* @see "https://developers.braintreepayments.com/reference/general/validation-errors/all/java"
|
||||
*/
|
||||
private Map<String, Object> handleValidationErrorResponse(ValidationErrors validationErrors) {
|
||||
List<ValidationError> errors = validationErrors.getAllDeepValidationErrors();
|
||||
verify(!errors.isEmpty(), "Payment failed but validation error list was empty");
|
||||
for (ValidationError error : errors) {
|
||||
logger.warningfmt("Payment validation failed on field: %s\nCode: %s\nMessage: %s",
|
||||
error.getAttribute(), error.getCode(), error.getMessage());
|
||||
}
|
||||
return JsonResponseHelper
|
||||
.createFormFieldError(errors.get(0).getMessage(), errors.get(0).getAttribute());
|
||||
}
|
||||
|
||||
private static String formatMoney(Money amount) {
|
||||
String symbol = amount.getCurrencyUnit().getSymbol(Locale.US);
|
||||
BigDecimal number = amount.getAmount().setScale(amount.getCurrencyUnit().getDecimalPlaces());
|
||||
return symbol.length() == 1 ? symbol + number : amount.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.ui.server.registrar;
|
||||
|
||||
import static com.google.common.base.Functions.toStringFunction;
|
||||
import static com.google.domain.registry.security.JsonResponseHelper.Status.ERROR;
|
||||
import static com.google.domain.registry.security.JsonResponseHelper.Status.SUCCESS;
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.domain.registry.braintree.BraintreeRegistrarSyncer;
|
||||
import com.google.domain.registry.config.ConfigModule.Config;
|
||||
import com.google.domain.registry.model.registrar.Registrar;
|
||||
import com.google.domain.registry.request.Action;
|
||||
import com.google.domain.registry.request.JsonActionRunner;
|
||||
import com.google.domain.registry.request.JsonActionRunner.JsonAction;
|
||||
import com.google.domain.registry.security.JsonResponseHelper;
|
||||
|
||||
import com.braintreegateway.BraintreeGateway;
|
||||
|
||||
import org.joda.money.CurrencyUnit;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Action returning information needed to render payment form in browser.
|
||||
*
|
||||
* <h3>Request Object</h3>
|
||||
*
|
||||
* <p>The request payload must be an empty JSON object.
|
||||
*
|
||||
* <h3>Response Object</h3>
|
||||
*
|
||||
* <p>The response payload will be a JSON response object (as defined by {@link JsonResponseHelper})
|
||||
* containing a single result object with the following fields:
|
||||
*
|
||||
* <dl>
|
||||
* <dt>brainframe
|
||||
* <dd>URL for iframe that loads Braintree payment method selector.
|
||||
* <dt>token
|
||||
* <dd>Nonce string obtained from the Braintree API which is needed by the Braintree JS SDK.
|
||||
* <dt>currencies
|
||||
* <dd>Array of strings, each containing a three letter currency code, which should be displayed to
|
||||
* the customer in a drop-down field. This will be all currencies for which a Braintree merchant
|
||||
* account exists. A currency will even be displayed if no TLD is enabled on the customer
|
||||
* account that bills in that currency.
|
||||
* </dl>
|
||||
*
|
||||
* <p><b>Note:</b> These definitions corresponds to Closure Compiler extern
|
||||
* {@code registry.rpc.PaymentSetup} which must be updated should these definitions change.
|
||||
*
|
||||
* @see RegistrarPaymentAction
|
||||
* @see "https://developers.braintreepayments.com/start/hello-server/java#generate-a-client-token"
|
||||
*/
|
||||
@Action(
|
||||
path = "/registrar-payment-setup",
|
||||
method = Action.Method.POST,
|
||||
xsrfProtection = true,
|
||||
xsrfScope = "console",
|
||||
requireLogin = true)
|
||||
public final class RegistrarPaymentSetupAction implements Runnable, JsonAction {
|
||||
|
||||
@Inject BraintreeGateway braintreeGateway;
|
||||
@Inject BraintreeRegistrarSyncer customerSyncer;
|
||||
@Inject JsonActionRunner jsonActionRunner;
|
||||
@Inject Registrar registrar;
|
||||
@Inject @Config("brainframe") String brainframe;
|
||||
@Inject @Config("braintreeMerchantAccountIds") ImmutableMap<CurrencyUnit, String> accountIds;
|
||||
@Inject RegistrarPaymentSetupAction() {}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
jsonActionRunner.run(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> handleJsonRequest(Map<String, ?> json) {
|
||||
if (!json.isEmpty()) {
|
||||
return JsonResponseHelper.create(ERROR, "JSON request object must be empty");
|
||||
}
|
||||
|
||||
// payment.js is hard-coded to display a specific SOY error template for certain error messages.
|
||||
if (registrar.getBillingMethod() != Registrar.BillingMethod.BRAINTREE) {
|
||||
// Registrar needs to contact support to have their billing bit flipped.
|
||||
return JsonResponseHelper.create(ERROR, "not-using-cc-billing");
|
||||
}
|
||||
|
||||
// In order to set the customerId field on the payment, the customer must exist.
|
||||
customerSyncer.sync(registrar);
|
||||
|
||||
return JsonResponseHelper
|
||||
.create(SUCCESS, "Success", asList(
|
||||
ImmutableMap.of(
|
||||
"brainframe", brainframe,
|
||||
"token", braintreeGateway.clientToken().generate(),
|
||||
"currencies",
|
||||
FluentIterable.from(accountIds.keySet())
|
||||
.transform(toStringFunction())
|
||||
.toList())));
|
||||
}
|
||||
}
|
261
java/google/registry/ui/server/registrar/RegistrarServlet.java
Normal file
261
java/google/registry/ui/server/registrar/RegistrarServlet.java
Normal file
|
@ -0,0 +1,261 @@
|
|||
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.ui.server.registrar;
|
||||
|
||||
import static com.google.common.collect.Iterables.any;
|
||||
import static com.google.common.collect.Iterables.concat;
|
||||
import static com.google.common.collect.Sets.difference;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.security.JsonResponseHelper.Status.SUCCESS;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.domain.registry.config.RegistryConfig;
|
||||
import com.google.domain.registry.config.RegistryEnvironment;
|
||||
import com.google.domain.registry.export.sheet.SyncRegistrarsSheetAction;
|
||||
import com.google.domain.registry.model.registrar.Registrar;
|
||||
import com.google.domain.registry.model.registrar.RegistrarContact;
|
||||
import com.google.domain.registry.security.JsonResponseHelper;
|
||||
import com.google.domain.registry.ui.forms.FormException;
|
||||
import com.google.domain.registry.ui.server.RegistrarFormFields;
|
||||
import com.google.domain.registry.util.CidrAddressBlock;
|
||||
import com.google.domain.registry.util.CollectionUtils;
|
||||
import com.google.domain.registry.util.DiffUtils;
|
||||
import com.google.domain.registry.util.SendEmailUtils;
|
||||
|
||||
import com.googlecode.objectify.Work;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* Admin servlet that allows creating or updating a registrar. Deletes are not allowed so as to
|
||||
* preserve history.
|
||||
*/
|
||||
public class RegistrarServlet extends ResourceServlet {
|
||||
|
||||
private static final RegistryConfig CONFIG = RegistryEnvironment.get().config();
|
||||
|
||||
private static final Predicate<RegistrarContact> HAS_PHONE = new Predicate<RegistrarContact>() {
|
||||
@Override
|
||||
public boolean apply(RegistrarContact contact) {
|
||||
return contact.getPhoneNumber() != null;
|
||||
}};
|
||||
|
||||
/** Thrown when a set of contacts doesn't meet certain constraints. */
|
||||
private static class ContactRequirementException extends FormException {
|
||||
ContactRequirementException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
ContactRequirementException(RegistrarContact.Type type) {
|
||||
super("Must have at least one " + type.getDisplayName() + " contact");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> read(HttpServletRequest req, Map<String, ?> args) {
|
||||
String clientId = sessionUtils.getRegistrarClientId(req);
|
||||
Registrar registrar = getCheckedRegistrar(clientId);
|
||||
return JsonResponseHelper.create(SUCCESS, "Success", registrar.toJsonMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
Map<String, Object> update(HttpServletRequest req, final Map<String, ?> args) {
|
||||
final String clientId = sessionUtils.getRegistrarClientId(req);
|
||||
return ofy().transact(new Work<Map<String, Object>>() {
|
||||
@Override
|
||||
public Map<String, Object> run() {
|
||||
Registrar existingRegistrar = getCheckedRegistrar(clientId);
|
||||
ImmutableSet<RegistrarContact> oldContacts = existingRegistrar.getContacts();
|
||||
Map<String, Object> existingRegistrarMap =
|
||||
expandRegistrarWithContacts(existingRegistrar, oldContacts);
|
||||
Registrar.Builder builder = existingRegistrar.asBuilder();
|
||||
ImmutableSet<RegistrarContact> updatedContacts = update(existingRegistrar, builder, args);
|
||||
if (!updatedContacts.isEmpty()) {
|
||||
builder.setContactsRequireSyncing(true);
|
||||
}
|
||||
Registrar updatedRegistrar = builder.build();
|
||||
ofy().save().entity(updatedRegistrar);
|
||||
if (!updatedContacts.isEmpty()) {
|
||||
checkContactRequirements(oldContacts, updatedContacts);
|
||||
RegistrarContact.updateContacts(updatedRegistrar, updatedContacts);
|
||||
}
|
||||
// Update the registrar map with updated contacts to bypass Objectify caching issues that
|
||||
// come into play with calling getContacts().
|
||||
Map<String, Object> updatedRegistrarMap =
|
||||
expandRegistrarWithContacts(updatedRegistrar, updatedContacts);
|
||||
sendExternalUpdatesIfNecessary(
|
||||
updatedRegistrar.getRegistrarName(),
|
||||
existingRegistrarMap,
|
||||
updatedRegistrarMap);
|
||||
return JsonResponseHelper.create(
|
||||
SUCCESS,
|
||||
"Saved " + clientId,
|
||||
updatedRegistrar.toJsonMap());
|
||||
}});
|
||||
}
|
||||
|
||||
private Map<String, Object> expandRegistrarWithContacts(
|
||||
Registrar registrar, Iterable<RegistrarContact> contacts) {
|
||||
ImmutableSet<Map<String, Object>> expandedContacts = FluentIterable.from(contacts)
|
||||
.transform(new Function<RegistrarContact, Map<String, Object>>() {
|
||||
@Override
|
||||
public Map<String, Object> apply(RegistrarContact contact) {
|
||||
return contact.toDiffableFieldMap();
|
||||
}})
|
||||
.toSet();
|
||||
// Use LinkedHashMap here to preserve ordering; null values mean we can't use ImmutableMap.
|
||||
LinkedHashMap<String, Object> result = new LinkedHashMap<>();
|
||||
result.putAll(registrar.toDiffableFieldMap());
|
||||
result.put("contacts", expandedContacts);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if any changes were made to the registrar besides the lastUpdateTime, and if so,
|
||||
* sends an email with a diff of the changes to the configured notification email address and
|
||||
* enqueues a task to re-sync the registrar sheet.
|
||||
*/
|
||||
private void sendExternalUpdatesIfNecessary(
|
||||
String registrarName,
|
||||
Map<String, Object> existingRegistrar,
|
||||
Map<String, Object> updatedRegistrar) {
|
||||
Map<?, ?> diffs = DiffUtils.deepDiff(existingRegistrar, updatedRegistrar);
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<String> changedKeys = (Set<String>) diffs.keySet();
|
||||
if (CollectionUtils.difference(changedKeys, "lastUpdateTime").isEmpty()) {
|
||||
return;
|
||||
}
|
||||
SyncRegistrarsSheetAction.enqueueBackendTask();
|
||||
ImmutableList<String> toEmailAddress = CONFIG.getRegistrarChangesNotificationEmailAddresses();
|
||||
if (!toEmailAddress.isEmpty()) {
|
||||
SendEmailUtils.sendEmail(
|
||||
toEmailAddress,
|
||||
String.format("Registrar %s updated", registrarName),
|
||||
"The following changes were made to the registrar:\n"
|
||||
+ DiffUtils.prettyPrintDiffedMap(diffs, null));
|
||||
}
|
||||
}
|
||||
|
||||
private Registrar getCheckedRegistrar(String clientId) {
|
||||
return checkExists(Registrar.loadByClientId(clientId),
|
||||
"No registrar exists with the given client id: " + clientId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforces business logic checks on registrar contacts.
|
||||
*
|
||||
* @throws FormException if the checks fail.
|
||||
*/
|
||||
void checkContactRequirements(
|
||||
Set<RegistrarContact> existingContacts, Set<RegistrarContact> updatedContacts) {
|
||||
// Check that no two contacts use the same email address.
|
||||
Set<String> emails = new HashSet<>();
|
||||
for (RegistrarContact contact : updatedContacts) {
|
||||
if (!emails.add(contact.getEmailAddress())) {
|
||||
throw new ContactRequirementException(String.format(
|
||||
"One email address (%s) cannot be used for multiple contacts",
|
||||
contact.getEmailAddress()));
|
||||
}
|
||||
}
|
||||
// Check that required contacts don't go away, once they are set.
|
||||
Multimap<RegistrarContact.Type, RegistrarContact> oldContactsByType = HashMultimap.create();
|
||||
for (RegistrarContact contact : existingContacts) {
|
||||
for (RegistrarContact.Type t : contact.getTypes()) {
|
||||
oldContactsByType.put(t, contact);
|
||||
}
|
||||
}
|
||||
Multimap<RegistrarContact.Type, RegistrarContact> newContactsByType = HashMultimap.create();
|
||||
for (RegistrarContact contact : updatedContacts) {
|
||||
for (RegistrarContact.Type t : contact.getTypes()) {
|
||||
newContactsByType.put(t, contact);
|
||||
}
|
||||
}
|
||||
for (RegistrarContact.Type t
|
||||
: difference(oldContactsByType.keySet(), newContactsByType.keySet())) {
|
||||
if (t.isRequired()) {
|
||||
throw new ContactRequirementException(t);
|
||||
}
|
||||
}
|
||||
// Ensure at least one tech contact has a phone number if one was present before.
|
||||
if (any(oldContactsByType.get(RegistrarContact.Type.TECH), HAS_PHONE)
|
||||
&& !any(newContactsByType.get(RegistrarContact.Type.TECH), HAS_PHONE)) {
|
||||
throw new ContactRequirementException(String.format(
|
||||
"At least one %s contact must have a phone number",
|
||||
RegistrarContact.Type.TECH.getDisplayName()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a registrar builder with the supplied args from the http request, and returns a list of
|
||||
* the new registrar contacts.
|
||||
*/
|
||||
public static ImmutableSet<RegistrarContact> update(
|
||||
Registrar existingRegistrarObj, Registrar.Builder builder, Map<String, ?> args) {
|
||||
|
||||
// WHOIS
|
||||
builder.setWhoisServer(
|
||||
RegistrarFormFields.WHOIS_SERVER_FIELD.extractUntyped(args).orNull());
|
||||
builder.setReferralUrl(
|
||||
RegistrarFormFields.REFERRAL_URL_FIELD.extractUntyped(args).orNull());
|
||||
for (String email :
|
||||
RegistrarFormFields.EMAIL_ADDRESS_FIELD.extractUntyped(args).asSet()) {
|
||||
builder.setEmailAddress(email);
|
||||
}
|
||||
builder.setPhoneNumber(
|
||||
RegistrarFormFields.PHONE_NUMBER_FIELD.extractUntyped(args).orNull());
|
||||
builder.setFaxNumber(
|
||||
RegistrarFormFields.FAX_NUMBER_FIELD.extractUntyped(args).orNull());
|
||||
builder.setLocalizedAddress(
|
||||
RegistrarFormFields.L10N_ADDRESS_FIELD.extractUntyped(args).orNull());
|
||||
|
||||
// Security
|
||||
builder.setIpAddressWhitelist(
|
||||
RegistrarFormFields.IP_ADDRESS_WHITELIST_FIELD.extractUntyped(args).or(
|
||||
ImmutableList.<CidrAddressBlock>of()));
|
||||
for (String certificate
|
||||
: RegistrarFormFields.CLIENT_CERTIFICATE_FIELD.extractUntyped(args).asSet()) {
|
||||
builder.setClientCertificate(certificate, ofy().getTransactionTime());
|
||||
}
|
||||
for (String certificate
|
||||
: RegistrarFormFields.FAILOVER_CLIENT_CERTIFICATE_FIELD.extractUntyped(args).asSet()) {
|
||||
builder.setFailoverClientCertificate(certificate, ofy().getTransactionTime());
|
||||
}
|
||||
|
||||
builder.setUrl(
|
||||
RegistrarFormFields.URL_FIELD.extractUntyped(args).orNull());
|
||||
builder.setReferralUrl(
|
||||
RegistrarFormFields.REFERRAL_URL_FIELD.extractUntyped(args).orNull());
|
||||
|
||||
// Contact
|
||||
ImmutableSet.Builder<RegistrarContact> contacts = new ImmutableSet.Builder<>();
|
||||
for (RegistrarContact.Builder contactBuilder
|
||||
: concat(RegistrarFormFields.CONTACTS_FIELD.extractUntyped(args).asSet())) {
|
||||
contacts.add(contactBuilder.setParent(existingRegistrarObj).build());
|
||||
}
|
||||
|
||||
return contacts.build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.ui.server.registrar;
|
||||
|
||||
import com.google.domain.registry.model.registrar.Registrar;
|
||||
import com.google.domain.registry.request.HttpException.ForbiddenException;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/** Registrar Console module providing reference to logged-in {@link Registrar}. */
|
||||
@Module
|
||||
public final class RegistrarUserModule {
|
||||
|
||||
@Provides
|
||||
static Registrar provideRegistrarUser(SessionUtils sessionUtils, HttpServletRequest req) {
|
||||
if (!sessionUtils.checkRegistrarConsoleLogin(req)) {
|
||||
throw new ForbiddenException("Not authorized to access Registrar Console");
|
||||
}
|
||||
return Registrar.loadByClientId(sessionUtils.getRegistrarClientId(req));
|
||||
}
|
||||
}
|
107
java/google/registry/ui/server/registrar/ResourceServlet.java
Normal file
107
java/google/registry/ui/server/registrar/ResourceServlet.java
Normal file
|
@ -0,0 +1,107 @@
|
|||
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.ui.server.registrar;
|
||||
|
||||
import static com.google.appengine.api.users.UserServiceFactory.getUserService;
|
||||
import static com.google.domain.registry.flows.EppConsoleServlet.XSRF_SCOPE;
|
||||
import static com.google.domain.registry.security.JsonResponseHelper.Status.ERROR;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.domain.registry.request.HttpException.NotFoundException;
|
||||
import com.google.domain.registry.security.JsonResponseHelper;
|
||||
import com.google.domain.registry.security.JsonTransportServlet;
|
||||
import com.google.domain.registry.ui.forms.FormException;
|
||||
import com.google.domain.registry.ui.forms.FormFieldException;
|
||||
import com.google.domain.registry.util.NonFinalForTesting;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/** A servlet for callbacks that manipulate resources. */
|
||||
public abstract class ResourceServlet extends JsonTransportServlet {
|
||||
|
||||
private static final String OP_PARAM = "op";
|
||||
private static final String ARGS_PARAM = "args";
|
||||
|
||||
@NonFinalForTesting
|
||||
protected static SessionUtils sessionUtils = new SessionUtils(getUserService());
|
||||
|
||||
public ResourceServlet() {
|
||||
super(XSRF_SCOPE, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> doJsonPost(HttpServletRequest req, Map<String, ?> params) {
|
||||
if (!sessionUtils.isLoggedIn()) {
|
||||
return JsonResponseHelper.create(ERROR, "Not logged in");
|
||||
}
|
||||
if (!sessionUtils.checkRegistrarConsoleLogin(req)) {
|
||||
return JsonResponseHelper.create(ERROR, "Not authorized to access Registrar Console");
|
||||
}
|
||||
String op = Optional.fromNullable((String) params.get(OP_PARAM)).or("read");
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, ?> args = (Map<String, Object>)
|
||||
Optional.<Object>fromNullable(params.get(ARGS_PARAM)).or(ImmutableMap.of());
|
||||
try {
|
||||
switch (op) {
|
||||
case "create":
|
||||
return create(req, args);
|
||||
case "update":
|
||||
return update(req, args);
|
||||
case "delete":
|
||||
return delete(req, args);
|
||||
case "read":
|
||||
return read(req, args);
|
||||
default:
|
||||
return JsonResponseHelper.create(ERROR, "Unknown operation: " + op);
|
||||
}
|
||||
} catch (FormFieldException e) {
|
||||
return JsonResponseHelper.createFormFieldError(e.getMessage(), e.getFieldName());
|
||||
} catch (FormException ee) {
|
||||
return JsonResponseHelper.create(ERROR, ee.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
Map<String, Object> create(HttpServletRequest req, Map<String, ?> args) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
Map<String, Object> read(HttpServletRequest req, Map<String, ?> args) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
Map<String, Object> update(HttpServletRequest req, Map<String, ?> args) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
Map<String, Object> delete(HttpServletRequest req, Map<String, ?> args) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/** Like checkNotNull, but throws NotFoundException if given arg is null. */
|
||||
protected static <T> T checkExists(@Nullable T obj, String msg) {
|
||||
if (obj == null) {
|
||||
throw new NotFoundException(msg);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
}
|
166
java/google/registry/ui/server/registrar/SessionUtils.java
Normal file
166
java/google/registry/ui/server/registrar/SessionUtils.java
Normal file
|
@ -0,0 +1,166 @@
|
|||
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.ui.server.registrar;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.base.Verify.verify;
|
||||
import static com.google.common.net.HttpHeaders.LOCATION;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_MOVED_TEMPORARILY;
|
||||
|
||||
import com.google.appengine.api.users.User;
|
||||
import com.google.appengine.api.users.UserService;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.google.domain.registry.model.registrar.Registrar;
|
||||
import com.google.domain.registry.model.registrar.RegistrarContact;
|
||||
import com.google.domain.registry.util.FormattingLogger;
|
||||
|
||||
import javax.annotation.CheckReturnValue;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
/** HTTP session management helper class. */
|
||||
@Immutable
|
||||
public class SessionUtils {
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
public static final String CLIENT_ID_ATTRIBUTE = "clientId";
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
@Inject
|
||||
public SessionUtils(UserService userService) {
|
||||
this.userService = checkNotNull(userService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects client to login URL if they aren't authenticated with the App Engine user service.
|
||||
*
|
||||
* @return {@code false} if request should abort.
|
||||
*/
|
||||
@CheckReturnValue
|
||||
public boolean redirectIfNotLoggedIn(HttpServletRequest req, HttpServletResponse rsp) {
|
||||
if (!isLoggedIn()) {
|
||||
logger.info("User not logged in to App Engine UserService.");
|
||||
rsp.setStatus(SC_MOVED_TEMPORARILY);
|
||||
rsp.setHeader(LOCATION, userService.createLoginURL(req.getRequestURI()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks GAE user has access to Registrar Console.
|
||||
*
|
||||
* <p>This routine will first check the HTTP session (creating one if it doesn't exist) for the
|
||||
* {@code clientId} attribute:
|
||||
*
|
||||
* <ul>
|
||||
* <li>If it does not exist, then we will attempt to guess the {@link Registrar} with which the
|
||||
* user's GAIA ID is associated. The {@code clientId} of the first matching {@code Registrar} will
|
||||
* then be stored to the the HTTP session.
|
||||
* <li>If it does exist, then we'll fetch the Registrar from the datastore to make sure access
|
||||
* wasn't revoked. This should only cost one memcache read.
|
||||
* </ul>
|
||||
*
|
||||
* <p><b>Note:</b> You should call {@link #redirectIfNotLoggedIn} before calling this method.
|
||||
*
|
||||
* @return {@code false} if user does not have access, in which case the caller should write an
|
||||
* error response and abort the request.
|
||||
*/
|
||||
@CheckReturnValue
|
||||
public boolean checkRegistrarConsoleLogin(HttpServletRequest req) {
|
||||
HttpSession session = req.getSession();
|
||||
User user = userService.getCurrentUser();
|
||||
checkState(user != null, "You forgot to call redirectIfNotLoggedIn()");
|
||||
String clientId = (String) session.getAttribute(CLIENT_ID_ATTRIBUTE);
|
||||
if (clientId == null) {
|
||||
Optional<Registrar> registrar = guessRegistrar(user.getUserId());
|
||||
if (!registrar.isPresent()) {
|
||||
logger.infofmt("User not associated with any Registrar: %s (%s)",
|
||||
user.getUserId(), user.getEmail());
|
||||
return false;
|
||||
}
|
||||
verify(hasAccessToRegistrar(registrar.get(), user.getUserId()));
|
||||
session.setAttribute(CLIENT_ID_ATTRIBUTE, registrar.get().getClientIdentifier());
|
||||
} else {
|
||||
if (!hasAccessToRegistrar(clientId, user.getUserId())) {
|
||||
logger.infofmt("Registrar Console access revoked: %s for %s (%s)",
|
||||
clientId, user.getEmail(), user.getUserId());
|
||||
session.invalidate();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link Registrar} clientId associated with HTTP session.
|
||||
*
|
||||
* @throws IllegalStateException if you forgot to call {@link #checkRegistrarConsoleLogin}.
|
||||
*/
|
||||
@CheckReturnValue
|
||||
public String getRegistrarClientId(HttpServletRequest req) {
|
||||
String clientId = (String) req.getSession().getAttribute(CLIENT_ID_ATTRIBUTE);
|
||||
checkState(clientId != null, "You forgot to call checkRegistrarConsoleLogin()");
|
||||
return clientId;
|
||||
}
|
||||
|
||||
/** @see UserService#isUserLoggedIn() */
|
||||
public boolean isLoggedIn() {
|
||||
return userService.isUserLoggedIn();
|
||||
}
|
||||
|
||||
/** Returns first {@link Registrar} that {@code gaeUserId} is authorized to administer. */
|
||||
private static Optional<Registrar> guessRegistrar(String gaeUserId) {
|
||||
RegistrarContact contact = ofy().load()
|
||||
.type(RegistrarContact.class)
|
||||
.filter("gaeUserId", gaeUserId)
|
||||
.first().now();
|
||||
if (contact == null) {
|
||||
return Optional.absent();
|
||||
}
|
||||
return Optional.of(ofy().load().key(contact.getParent()).safe());
|
||||
}
|
||||
|
||||
/** @see #hasAccessToRegistrar(Registrar, String) */
|
||||
private static boolean hasAccessToRegistrar(String clientId, final String gaeUserId) {
|
||||
Registrar registrar = Registrar.loadByClientId(clientId);
|
||||
if (registrar == null) {
|
||||
logger.warningfmt("Registrar '%s' disappeared from the datastore!", clientId);
|
||||
return false;
|
||||
}
|
||||
return hasAccessToRegistrar(registrar, gaeUserId);
|
||||
}
|
||||
|
||||
/** Returns {@code true} if {@code gaeUserId} is listed in contacts. */
|
||||
private static boolean hasAccessToRegistrar(Registrar registrar, final String gaeUserId) {
|
||||
return FluentIterable
|
||||
.from(registrar.getContacts())
|
||||
.anyMatch(new Predicate<RegistrarContact>() {
|
||||
@Override
|
||||
public boolean apply(@Nonnull RegistrarContact contact) {
|
||||
return gaeUserId.equals(contact.getGaeUserId());
|
||||
}});
|
||||
}
|
||||
}
|
16
java/google/registry/ui/server/registrar/package-info.java
Normal file
16
java/google/registry/ui/server/registrar/package-info.java
Normal file
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
@javax.annotation.ParametersAreNonnullByDefault
|
||||
package com.google.domain.registry.ui.server.registrar;
|
Loading…
Add table
Add a link
Reference in a new issue