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:
Justine Tunney 2016-05-13 18:55:08 -04:00
parent a41677aea1
commit 5012893c1d
2396 changed files with 0 additions and 0 deletions

View 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",
],
)

View 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();
}
};
}
}

View 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() {}
}

View 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() {}
}

View 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",
],
)

View 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);
}
}

View 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;

View 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;

View 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"],
)

View 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());
}
}

View file

@ -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();
}
}

View file

@ -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())));
}
}

View 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();
}
}

View file

@ -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));
}
}

View 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;
}
}

View 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());
}});
}
}

View 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;