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,64 @@
// 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.request;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** Annotation for {@link Runnable} actions accepting HTTP requests from {@link RequestHandler}. */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Action {
/** HTTP methods recognized by the request processor. */
public enum Method { GET, HEAD, POST }
/** HTTP path to serve the action from. The path components must be percent-escaped. */
String path();
/** Indicates all paths starting with this path should be accepted. */
boolean isPrefix() default false;
/** HTTP methods that request processor should allow. */
Method[] method() default Method.GET;
/**
* Indicates request processor should print "OK" to the HTTP client on success.
*
* <p>This is important because it's confusing to manually invoke a backend task and have a blank
* page show up. And it's not worth injecting a {@link Response} object just to do something so
* trivial.
*/
boolean automaticallyPrintOk() default false;
// TODO(b/26304887): Flip default to true.
/** Enables XSRF protection on all HTTP methods except GET and HEAD. */
boolean xsrfProtection() default false;
/** Arbitrary value included in the XSRF token hash. */
String xsrfScope() default "app";
/**
* Require user be logged-in or 302 redirect to the Google auth login page.
*
* <p><b>Warning:</b> DO NOT use this for cron and task queue endpoints.
*
* <p><b>Note:</b> Logged-in actions should also be guarded by a {@code <security-constraint>} in
* {@code web.xml} with {@code <role-name>*</role-name>}.
*/
boolean requireLogin() default false;
}

View file

@ -0,0 +1,31 @@
// 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.request;
import static com.google.common.base.Preconditions.checkArgument;
/** Helper methods for working with {@link Action @Action}-annotated classes. */
public final class Actions {
/** Extracts the action path from the {@link Action @Action} annotation. */
public static String getPathForAction(Class<?> action) {
checkArgument(
action.isAnnotationPresent(Action.class),
"Cannot get path of class without @Action annotation: %s", action);
return action.getAnnotation(Action.class).path();
}
private Actions() {}
}

View file

@ -0,0 +1,48 @@
package(default_visibility = ["//java/com/google/domain/registry:registry_project"])
java_library(
name = "request",
srcs = glob(
["*.java"],
exclude = ["Modules.java"],
),
visibility = ["//visibility:public"],
deps = [
"//java/com/google/common/annotations",
"//java/com/google/common/base",
"//java/com/google/common/collect",
"//java/com/google/common/html",
"//java/com/google/common/io",
"//java/com/google/common/net",
"//java/com/google/domain/registry/security",
"//java/com/google/domain/registry/util",
"//third_party/java/appengine:appengine-api",
"//third_party/java/auto:auto_value",
"//third_party/java/dagger",
"//third_party/java/joda_time",
"//third_party/java/json_simple",
"//third_party/java/jsr305_annotations",
"//third_party/java/jsr330_inject",
"//third_party/java/servlet/servlet_api",
],
)
java_library(
name = "modules",
srcs = ["Modules.java"],
visibility = ["//visibility:public"],
deps = [
":request",
"//java/com/google/api/client/extensions/appengine/http",
"//java/com/google/api/client/googleapis/auth/oauth2",
"//java/com/google/api/client/googleapis/extensions/appengine/auth/oauth2",
"//java/com/google/api/client/http",
"//java/com/google/api/client/json",
"//java/com/google/api/client/json/jackson2",
"//java/com/google/domain/registry/config",
"//java/com/google/domain/registry/keyring/api",
"//third_party/java/appengine:appengine-api",
"//third_party/java/dagger",
],
)

View file

@ -0,0 +1,28 @@
// 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.request;
import java.lang.annotation.Documented;
import java.util.Set;
import javax.inject.Qualifier;
/**
* Dagger qualifier for the {@link Set} of OAuth2 scope strings used for authorization on APIs that
* are connected to using a delegated user account (the serviceAccountUser in GoogleCredential).
*/
@Qualifier
@Documented
public @interface DelegatedOAuthScopes {}

View file

@ -0,0 +1,26 @@
// 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.request;
import java.lang.annotation.Documented;
import javax.inject.Qualifier;
/** Dagger qualifier for HTTP headers. */
@Qualifier
@Documented
public @interface Header {
String value();
}

View file

@ -0,0 +1,229 @@
// 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.request;
import static com.google.common.html.HtmlEscapers.htmlEscaper;
import com.google.domain.registry.util.FormattingLogger;
import java.io.IOException;
import javax.servlet.http.HttpServletResponse;
/** Base for exceptions that cause an HTTP error response. */
public abstract class HttpException extends RuntimeException {
// as per https://tools.ietf.org/html/rfc4918
private static final int SC_UNPROCESSABLE_ENTITY = 422;
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
private final int responseCode;
protected HttpException(int responseCode, String message, Throwable cause) {
super(message, cause);
this.responseCode = responseCode;
}
public final int getResponseCode() {
return responseCode;
}
/**
* Returns the string associated with a particular response code. Unlike {@link #getMessage()},
* which is meant to describe the circumstances under which this particular exception occurred,
* this method always returns the same string (e.g. "Not Found" for a 400 error), and so should
* always be the same for a given subclass of {@link HttpException}.
*/
public String getResponseCodeString() {
return "Response Code " + responseCode;
}
/**
* Transmits the error response to the client.
*
* <p>{@link #getMessage()} will be sent to the browser, whereas {@link #toString()} and
* {@link #getCause()} will be logged.
*/
public final void send(HttpServletResponse rsp) throws IOException {
rsp.sendError(getResponseCode(), htmlEscaper().escape(getMessage()));
logger.infofmt(getCause(), "%s", this);
}
/**
* Exception that causes a 204 response.
*
* <p>This is useful for App Engine task queue handlers that want to display an error, but don't
* want the task to automatically retry, since the status code is less than 300.
*/
public static final class NoContentException extends HttpException {
public NoContentException(String message) {
super(HttpServletResponse.SC_NO_CONTENT, message, null);
}
@Override
public String getResponseCodeString() {
return "No Content";
}
}
/** Exception that causes a 304 response. */
public static final class NotModifiedException extends HttpException {
public NotModifiedException() {
this("Not Modified");
}
public NotModifiedException(String message) {
super(HttpServletResponse.SC_NOT_FOUND, message, null);
}
@Override
public String getResponseCodeString() {
return "Not Modified";
}
}
/** Exception that causes a 400 response. */
public static final class BadRequestException extends HttpException {
public BadRequestException(String message) {
this(message, null);
}
public BadRequestException(String message, Throwable cause) {
super(HttpServletResponse.SC_BAD_REQUEST, message, cause);
}
@Override
public String getResponseCodeString() {
return "Bad Request";
}
}
/** Exception that causes a 403 response. */
public static final class ForbiddenException extends HttpException {
public ForbiddenException(String message) {
super(HttpServletResponse.SC_FORBIDDEN, message, null);
}
@Override
public String getResponseCodeString() {
return "Forbidden";
}
}
/** Exception that causes a 404 response. */
public static final class NotFoundException extends HttpException {
public NotFoundException() {
this("Not found");
}
public NotFoundException(String message) {
super(HttpServletResponse.SC_NOT_FOUND, message, null);
}
@Override
public String getResponseCodeString() {
return "Not Found";
}
}
/** Exception that causes a 405 response. */
public static final class MethodNotAllowedException extends HttpException {
public MethodNotAllowedException() {
super(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "Method not allowed", null);
}
@Override
public String getResponseCodeString() {
return "Method Not Allowed";
}
}
/** Exception that causes a 409 response. */
public static final class ConflictException extends HttpException {
public ConflictException(String message) {
super(HttpServletResponse.SC_CONFLICT, message, null);
}
@Override
public String getResponseCodeString() {
return "Conflict";
}
}
/** Exception that causes a 415 response. */
public static final class UnsupportedMediaTypeException extends HttpException {
public UnsupportedMediaTypeException(String message) {
super(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE, message, null);
}
public UnsupportedMediaTypeException(String message, Throwable cause) {
super(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE, message, cause);
}
@Override
public String getResponseCodeString() {
return "Unsupported Media Type";
}
}
/** Exception that causes a 422 response. */
public static final class UnprocessableEntityException extends HttpException {
public UnprocessableEntityException(String message) {
super(SC_UNPROCESSABLE_ENTITY, message, null);
}
@Override
public String getResponseCodeString() {
return "Unprocessable Entity";
}
}
/** Exception that causes a 500 response. */
public static final class InternalServerErrorException extends HttpException {
public InternalServerErrorException(String message) {
super(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, message, null);
}
@Override
public String getResponseCodeString() {
return "Internal Server Error";
}
}
/** Exception that causes a 501 response. */
public static final class NotImplementedException extends HttpException {
public NotImplementedException(String message) {
super(HttpServletResponse.SC_NOT_IMPLEMENTED, message, null);
}
@Override
public String getResponseCodeString() {
return "Not Implemented";
}
}
/** Exception that causes a 503 response. */
public static final class ServiceUnavailableException extends HttpException {
public ServiceUnavailableException(String message) {
super(HttpServletResponse.SC_SERVICE_UNAVAILABLE, message, null);
}
@Override
public String getResponseCodeString() {
return "Service Unavailable";
}
}
}

View file

@ -0,0 +1,50 @@
// 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.request;
import static com.google.common.base.Verify.verifyNotNull;
import java.util.Map;
import javax.inject.Inject;
/** Runner for actions that read and write JSON objects. */
public final class JsonActionRunner {
/** Interface for actions that read and write JSON objects. */
public interface JsonAction {
/**
* Handles JSON HTTP request.
*
* @param json object extracted from request body
* @return an arbitrary JSON object, which is never {@code null}
* @throws HttpException to send a non-200 status code / message to client
*/
Map<String, ?> handleJsonRequest(Map<String, ?> json);
}
@Inject @JsonPayload Map<String, Object> payload;
@Inject JsonResponse response;
@Inject JsonActionRunner() {}
/** Delegates request to {@code action}. */
public void run(JsonAction action) {
response.setPayload(
verifyNotNull(
action.handleJsonRequest(payload),
"handleJsonRequest() returned null"));
}
}

View file

@ -0,0 +1,28 @@
// 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.request;
import java.lang.annotation.Documented;
import javax.inject.Qualifier;
/**
* Dagger qualifier for the HTTP request payload as parsed JSON.
*
* @see RequestModule
*/
@Qualifier
@Documented
public @interface JsonPayload {}

View file

@ -0,0 +1,68 @@
// 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.request;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.net.HttpHeaders.CONTENT_DISPOSITION;
import static com.google.common.net.HttpHeaders.X_CONTENT_TYPE_OPTIONS;
import static com.google.common.net.MediaType.JSON_UTF_8;
import static org.json.simple.JSONValue.toJSONString;
import org.joda.time.DateTime;
import java.util.Map;
import javax.inject.Inject;
/** JSON response object. */
public class JsonResponse {
/** String prefixed to all JSON-like responses to break {@code eval()}. */
public static final String JSON_SAFETY_PREFIX = ")]}'\n";
protected final Response response;
@Inject
public JsonResponse(Response rsp) {
this.response = rsp;
}
/** @see Response#setStatus */
public void setStatus(int status) {
response.setStatus(status);
}
/** Writes the JSON map to the HTTP payload; call this exactly once. */
public void setPayload(Map<String, ?> responseMap) {
response.setContentType(JSON_UTF_8);
// This prevents IE from MIME-sniffing a response away from the declared Content-Type.
response.setHeader(X_CONTENT_TYPE_OPTIONS, "nosniff");
// This is a defense in depth that prevents browsers from trying to render the content of the
// response, even if all else fails. It's basically another anti-sniffing mechanism in the sense
// that if you hit this url directly, it would try to download the file instead of showing it.
response.setHeader(CONTENT_DISPOSITION, "attachment");
response.setPayload(JSON_SAFETY_PREFIX + toJSONString(checkNotNull(responseMap)));
}
/** @see Response#setHeader */
public void setHeader(String header, String value) {
response.setHeader(header, value);
}
/** @see Response#setDateHeader */
public void setDateHeader(String header, DateTime timestamp) {
response.setDateHeader(header, timestamp);
}
}

View file

@ -0,0 +1,202 @@
// 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.request;
import static com.google.appengine.api.datastore.DatastoreServiceFactory.getDatastoreService;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.api.client.extensions.appengine.http.UrlFetchTransport;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.extensions.appengine.auth.oauth2.AppIdentityCredential;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.modules.ModulesService;
import com.google.appengine.api.modules.ModulesServiceFactory;
import com.google.appengine.api.urlfetch.URLFetchService;
import com.google.appengine.api.urlfetch.URLFetchServiceFactory;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.domain.registry.config.ConfigModule.Config;
import com.google.domain.registry.keyring.api.KeyModule.Key;
import dagger.Module;
import dagger.Provides;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Set;
import javax.inject.Named;
import javax.inject.Singleton;
/** Dagger modules for App Engine services and other vendor classes. */
public final class Modules {
/** Dagger module for {@link DatastoreService}. */
@Module
public static final class DatastoreServiceModule {
private static final DatastoreService datastoreService = getDatastoreService();
@Provides
static DatastoreService provideDatastoreService() {
return datastoreService;
}
}
/** Dagger module for {@link ModulesService}. */
@Module
public static final class ModulesServiceModule {
private static final ModulesService modulesService = ModulesServiceFactory.getModulesService();
@Provides
static ModulesService provideModulesService() {
return modulesService;
}
}
/** Dagger module for {@link URLFetchService}. */
@Module
public static final class URLFetchServiceModule {
private static final URLFetchService fetchService = URLFetchServiceFactory.getURLFetchService();
@Provides
static URLFetchService provideURLFetchService() {
return fetchService;
}
}
/** Dagger module for {@link UserService}. */
@Module
public static final class UserServiceModule {
private static final UserService userService = UserServiceFactory.getUserService();
@Provides
static UserService provideUserService() {
return userService;
}
}
/**
* Dagger module that causes the Jackson2 JSON parser to be used for Google APIs requests.
*
* <p>Jackson1 and GSON can also satisfy the {@link JsonFactory} interface, but we've decided to
* go with Jackson2, since it's what's used in the public examples for using Google APIs.
*/
@Module
public static final class Jackson2Module {
@Provides
static JsonFactory provideJsonFactory() {
return JacksonFactory.getDefaultInstance();
}
}
/** Dagger module that causes the App Engine's URL fetcher to be used for Google APIs requests. */
@Module
public static final class UrlFetchTransportModule {
private static final UrlFetchTransport HTTP_TRANSPORT = new UrlFetchTransport();
@Provides
static HttpTransport provideHttpTransport() {
return HTTP_TRANSPORT;
}
}
/**
* Dagger module providing {@link AppIdentityCredential}.
*
* <p>This can be used to authenticate to Google APIs using the identity of your GAE app.
*
* @see UseAppIdentityCredentialForGoogleApisModule
*/
@Module
public static final class AppIdentityCredentialModule {
@Provides
static AppIdentityCredential provideAppIdentityCredential(@OAuthScopes Set<String> scopes) {
return new AppIdentityCredential(scopes);
}
}
/**
* Dagger module causing Google APIs requests to be authorized with your GAE app identity.
*
* <p>You must also use the {@link AppIdentityCredential} module.
*/
@Module
public static final class UseAppIdentityCredentialForGoogleApisModule {
@Provides
static HttpRequestInitializer provideHttpRequestInitializer(AppIdentityCredential credential) {
return credential;
}
}
/**
* Dagger module providing {@link GoogleCredential} from a JSON key file contents.
*
* <p>This satisfies the {@link HttpRequestInitializer} interface for authenticating Google APIs
* requests, just like {@link AppIdentityCredential}.
*
* <p>But we consider GAE authentication more desirable and easier to manage operations-wise. So
* this authentication method should only be used for the following situations:
*
* <ol>
* <li>Locally-running programs (which aren't executing on the App Engine platform)
* <li>Spreadsheet service (which can't use {@link AppIdentityCredential} due to an old library)
* </ol>
*
* @see com.google.domain.registry.keyring.api.Keyring#getJsonCredential()
*/
@Module
public static final class GoogleCredentialModule {
@Provides
static GoogleCredential provideGoogleCredential(
HttpTransport httpTransport,
JsonFactory jsonFactory,
@Key("jsonCredential") String jsonCredential) {
try {
return GoogleCredential.fromStream(
new ByteArrayInputStream(jsonCredential.getBytes(UTF_8)), httpTransport, jsonFactory);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Provides a GoogleCredential that will connect to GAE using delegated admin access. This is
* needed for API calls requiring domain admin access to the relevant GAFYD using delegated
* scopes, e.g. the Directory API and the Groupssettings API.
*/
@Provides
@Singleton
@Named("delegatedAdmin")
static GoogleCredential provideDelegatedAdminGoogleCredential(
GoogleCredential googleCredential,
HttpTransport httpTransport,
@DelegatedOAuthScopes Set<String> scopes,
@Config("googleAppsAdminEmailAddress") String googleAppsAdminEmailAddress) {
return new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(googleCredential.getJsonFactory())
.setServiceAccountId(googleCredential.getServiceAccountId())
.setServiceAccountPrivateKey(googleCredential.getServiceAccountPrivateKey())
.setServiceAccountScopes(scopes)
.setServiceAccountUser(googleAppsAdminEmailAddress)
.build();
}
}
}

View file

@ -0,0 +1,25 @@
// 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.request;
import java.lang.annotation.Documented;
import java.util.Set;
import javax.inject.Qualifier;
/** Dagger qualifier for the {@link Set} of OAuth2 scope strings, used for API authorization. */
@Qualifier
@Documented
public @interface OAuthScopes {}

View file

@ -0,0 +1,26 @@
// 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.request;
import java.lang.annotation.Documented;
import javax.inject.Qualifier;
/** Dagger qualifier for HTTP parameters. */
@Qualifier
@Documented
public @interface Parameter {
String value();
}

View file

@ -0,0 +1,24 @@
// 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.request;
import java.lang.annotation.Documented;
import javax.inject.Qualifier;
/** Dagger qualifier for {@link com.google.common.collect.ImmutableListMultimap} of HTTP params. */
@Qualifier
@Documented
public @interface ParameterMap {}

View file

@ -0,0 +1,28 @@
// 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.request;
import java.lang.annotation.Documented;
import javax.inject.Qualifier;
/**
* Dagger qualifier for the HTTP request payload.
*
* @see RequestModule
*/
@Qualifier
@Documented
public @interface Payload {}

View file

@ -0,0 +1,159 @@
// 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.request;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.nullToEmpty;
import static com.google.common.net.HttpHeaders.LOCATION;
import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
import static com.google.domain.registry.security.XsrfTokenManager.X_CSRF_TOKEN;
import static com.google.domain.registry.security.XsrfTokenManager.validateToken;
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED;
import static javax.servlet.http.HttpServletResponse.SC_MOVED_TEMPORARILY;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.common.base.Optional;
import com.google.domain.registry.util.FormattingLogger;
import com.google.domain.registry.util.NonFinalForTesting;
import org.joda.time.Duration;
import java.io.IOException;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Dagger request processor for Domain Registry.
*
* <p>This class creates an HTTP request processor from a Dagger component. It routes requests from
* your servlet to an {@link Action @Action} annotated handler class.
*
* <h3>Component Definition</h3>
*
* <p>Action instances are supplied on a per-request basis by invoking the methods on {@code C}.
* For example:
* <pre>
* {@literal @Component}
* interface ServerComponent {
* HelloAction helloAction();
* }</pre>
*
* <p>The rules for component methods are as follows:
* <ol>
* <li>Methods whose raw return type does not implement {@code Runnable} will be ignored
* <li>Methods whose raw return type does not have an {@code @Action} annotation are ignored
* </ol>
*
* <h3>Security Features</h3>
*
* <p>XSRF protection is built into this class. It can be enabled or disabled on individual actions
* using {@link Action#xsrfProtection() xsrfProtection} setting.
*
* <p>This class also enforces the {@link Action#requireLogin() requireLogin} setting.
*
* @param <C> component type
*/
public final class RequestHandler<C> {
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
private static final Duration XSRF_VALIDITY = Duration.standardDays(1);
@NonFinalForTesting
private static UserService userService = UserServiceFactory.getUserService();
/**
* Creates a new request processor based off your component methods.
*
* <p><b>Warning:</b> When using the App Engine platform, you must call
* {@link Method#setAccessible(boolean) setAccessible(true)} on all your component {@link Method}
* instances, from within the same package as the component. This is due to cross-package
* reflection restrictions.
*
* @param methods is the result of calling {@link Class#getMethods()} on {@code component}, which
* are filtered to only include those with no arguments returning a {@link Runnable} with an
* {@link Action} annotation
*/
public static <C> RequestHandler<C> create(Class<C> component, Iterable<Method> methods) {
return new RequestHandler<>(component, Router.create(methods));
}
private final Router router;
private RequestHandler(Class<C> component, Router router) {
checkNotNull(component);
this.router = router;
}
/**
* Runs the appropriate action for a servlet request.
*
* @param component is an instance of the component type whose methods were passed to
* {@link #create(Class, Iterable)}
*/
public void handleRequest(HttpServletRequest req, HttpServletResponse rsp, C component)
throws IOException {
checkNotNull(component);
checkNotNull(rsp);
Action.Method method;
try {
method = Action.Method.valueOf(req.getMethod());
} catch (IllegalArgumentException e) {
logger.infofmt("Unsupported method: %s", req.getMethod());
rsp.sendError(SC_METHOD_NOT_ALLOWED);
return;
}
String path = req.getRequestURI();
Optional<Route> route = router.route(path);
if (!route.isPresent()) {
logger.infofmt("No action found for: %s", path);
rsp.sendError(SC_NOT_FOUND);
return;
}
if (!route.get().isMethodAllowed(method)) {
logger.infofmt("Method %s not allowed for: %s", method, path);
rsp.sendError(SC_METHOD_NOT_ALLOWED);
return;
}
if (route.get().action().requireLogin() && !userService.isUserLoggedIn()) {
logger.info("not logged in");
rsp.setStatus(SC_MOVED_TEMPORARILY);
rsp.setHeader(LOCATION, userService.createLoginURL(req.getRequestURI()));
return;
}
if (route.get().shouldXsrfProtect(method)
&& !validateToken(
nullToEmpty(req.getHeader(X_CSRF_TOKEN)),
route.get().action().xsrfScope(),
XSRF_VALIDITY)) {
rsp.sendError(SC_FORBIDDEN, "Invalid " + X_CSRF_TOKEN);
return;
}
try {
route.get().instantiator().apply(component).run();
if (route.get().action().automaticallyPrintOk()) {
rsp.setContentType(PLAIN_TEXT_UTF_8.toString());
rsp.getWriter().write("OK\n");
}
} catch (HttpException e) {
e.send(rsp);
}
}
}

View file

@ -0,0 +1,28 @@
// 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.request;
import java.lang.annotation.Documented;
import javax.inject.Qualifier;
/**
* Dagger qualifier for the HTTP request method.
*
* @see javax.servlet.http.HttpServletRequest#getMethod()
*/
@Qualifier
@Documented
public @interface RequestMethod {}

View file

@ -0,0 +1,154 @@
// 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.request;
import static com.google.common.net.MediaType.JSON_UTF_8;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams;
import com.google.common.net.MediaType;
import com.google.domain.registry.request.HttpException.BadRequestException;
import com.google.domain.registry.request.HttpException.UnsupportedMediaTypeException;
import dagger.Module;
import dagger.Provides;
import org.json.simple.JSONValue;
import org.json.simple.parser.ParseException;
import java.io.IOException;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/** Dagger module for servlets. */
@Module
public final class RequestModule {
private final HttpServletRequest req;
private final HttpServletResponse rsp;
public RequestModule(HttpServletRequest req, HttpServletResponse rsp) {
this.req = req;
this.rsp = rsp;
}
@Provides
static Response provideResponse(ResponseImpl response) {
return response;
}
@Provides
HttpServletRequest provideHttpServletRequest() {
return req;
}
@Provides
HttpServletResponse provideHttpServletResponse() {
return rsp;
}
@Provides
@RequestPath
static String provideRequestPath(HttpServletRequest req) {
return req.getRequestURI();
}
@Provides
@RequestMethod
static Action.Method provideRequestMethod(HttpServletRequest req) {
return Action.Method.valueOf(req.getMethod());
}
@Provides
@Header("Content-Type")
static MediaType provideContentType(HttpServletRequest req) {
try {
return MediaType.parse(req.getContentType());
} catch (IllegalArgumentException | NullPointerException e) {
throw new UnsupportedMediaTypeException("Bad Content-Type header", e);
}
}
@Provides
@Payload
static String providePayloadAsString(HttpServletRequest req) {
try {
return CharStreams.toString(req.getReader());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Provides
@Payload
static byte[] providePayloadAsBytes(HttpServletRequest req) {
try {
return ByteStreams.toByteArray(req.getInputStream());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Provides
@JsonPayload
@SuppressWarnings("unchecked")
static Map<String, Object> provideJsonPayload(
@Header("Content-Type") MediaType contentType,
@Payload String payload) {
if (!JSON_UTF_8.is(contentType.withCharset(UTF_8))) {
throw new UnsupportedMediaTypeException(
String.format("Expected %s Content-Type", JSON_UTF_8.withoutParameters()));
}
try {
return (Map<String, Object>) JSONValue.parseWithException(payload);
} catch (ParseException e) {
throw new BadRequestException(
"Malformed JSON", new VerifyException("Malformed JSON:\n" + payload, e));
}
}
/**
* Provides an immutable representation of the servlet request parameters.
*
* <p>This performs a shallow copy of the {@code Map<String, String[]>} data structure from the
* servlets API, each time this is provided. This is almost certainly less expensive than the
* thread synchronization expense of {@link javax.inject.Singleton @Singleton}.
*
* <p><b>Note:</b> If a parameter is specified without a value, e.g. {@code /foo?lol} then an
* empty string value is assumed, since Guava's multimap doesn't permit {@code null} mappings.
*
* @see HttpServletRequest#getParameterMap()
*/
@Provides
@ParameterMap
static ImmutableListMultimap<String, String> provideParameterMap(HttpServletRequest req) {
ImmutableListMultimap.Builder<String, String> params = new ImmutableListMultimap.Builder<>();
@SuppressWarnings("unchecked") // Safe by specification.
Map<String, String[]> original = req.getParameterMap();
for (Map.Entry<String, String[]> param : original.entrySet()) {
if (param.getValue().length == 0) {
params.put(param.getKey(), "");
} else {
params.putAll(param.getKey(), param.getValue());
}
}
return params.build();
}
}

View file

@ -0,0 +1,201 @@
// 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.request;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.base.Strings.nullToEmpty;
import com.google.common.base.Ascii;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.InetAddresses;
import com.google.domain.registry.request.HttpException.BadRequestException;
import org.joda.time.DateTime;
import java.net.InetAddress;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
/** Utilities for extracting parameters from HTTP requests. */
public final class RequestParameters {
/** The standardized request parameter name used by any servlet that takes a tld parameter. */
public static final String PARAM_TLD = "tld";
/**
* Returns first GET or POST parameter associated with {@code name}.
*
* <p>For example, assume {@code name} is "bar". The following request URIs would cause this
* method to yield the following results:
*
* <ul>
* <li>/foo?bar=hello hello
* <li>/foo?bar=hello&bar=there hello
* <li>/foo?bar= 400 error (empty)
* <li>/foo?bar=&bar=there 400 error (empty)
* <li>/foo 400 error (absent)
* </ul>
*
* @throws BadRequestException if request parameter is absent or empty
*/
public static String extractRequiredParameter(HttpServletRequest req, String name) {
String result = req.getParameter(name);
if (isNullOrEmpty(result)) {
throw new BadRequestException("Missing parameter: " + name);
}
return result;
}
/**
* Returns first GET or POST parameter associated with {@code name}.
*
* @throws BadRequestException if request parameter is absent (but not if empty)
*/
public static String extractRequiredMaybeEmptyParameter(HttpServletRequest req, String name) {
String result = req.getParameter(name);
if (result == null) {
throw new BadRequestException("Missing parameter: " + name);
}
return result;
}
/** Returns the first GET or POST parameter associated with {@code name}. */
public static Optional<String> extractOptionalParameter(HttpServletRequest req, String name) {
return Optional.fromNullable(emptyToNull(req.getParameter(name)));
}
/**
* Returns first GET or POST parameter associated with {@code name} as an integer.
*
* @throws BadRequestException if request parameter is present but not a valid integer
*/
public static Optional<Integer> extractOptionalIntParameter(HttpServletRequest req, String name) {
String stringParam = req.getParameter(name);
try {
return isNullOrEmpty(stringParam)
? Optional.<Integer>absent()
: Optional.of(Integer.valueOf(stringParam));
} catch (NumberFormatException e) {
throw new BadRequestException("Expected integer: " + name);
}
}
/**
* Returns first GET or POST parameter associated with {@code name} as an integer.
*
* @throws BadRequestException if request parameter is absent, empty, or not a valid integer
*/
public static int extractIntParameter(HttpServletRequest req, String name) {
try {
return Integer.valueOf(nullToEmpty(req.getParameter(name)));
} catch (NumberFormatException e) {
throw new BadRequestException("Expected integer: " + name);
}
}
/** Returns all GET or POST parameters associated with {@code name}. */
public static ImmutableSet<String> extractSetOfParameters(HttpServletRequest req, String name) {
String[] parameters = req.getParameterValues(name);
return parameters == null ? ImmutableSet.<String>of() : ImmutableSet.copyOf(parameters);
}
/**
* Returns the first GET or POST parameter associated with {@code name}.
*
* @throws BadRequestException if request parameter named {@code name} is absent, empty, or not
* equal to any of the values in {@code enumClass}
*/
public static <C extends Enum<C>>
C extractEnumParameter(HttpServletRequest req, Class<C> enumClass, String name) {
try {
return Enum.valueOf(enumClass, Ascii.toUpperCase(extractRequiredParameter(req, name)));
} catch (IllegalArgumentException e) {
throw new BadRequestException(
String.format("Invalid %s parameter: %s", enumClass.getSimpleName(), name));
}
}
/**
* Returns {@code true} if parameter is present and not empty and not {@code "false"}.
*
* <p>This considers a parameter with a non-existent value true, for situations where the request
* URI is something like {@code /foo?bar}, where the mere presence of the {@code bar} parameter
* without a value indicates that it's true.
*/
public static boolean extractBooleanParameter(HttpServletRequest req, String name) {
return req.getParameterMap().containsKey(name)
&& !equalsFalse(req.getParameter(name));
}
/**
* Returns first request parameter associated with {@code name} parsed as an
* <a href="https://goo.gl/pk5Q2k">ISO 8601</a> timestamp, e.g. {@code 1984-12-18TZ},
* {@code 2000-01-01T16:20:00Z}.
*
* @throws BadRequestException if request parameter named {@code name} is absent, empty, or could
* not be parsed as an ISO 8601 timestamp
*/
public static DateTime extractRequiredDatetimeParameter(HttpServletRequest req, String name) {
String stringValue = extractRequiredParameter(req, name);
try {
return DateTime.parse(stringValue);
} catch (IllegalArgumentException e) {
throw new BadRequestException("Bad ISO 8601 timestamp: " + name);
}
}
/**
* Returns first request parameter associated with {@code name} parsed as an optional
* {@link InetAddress} (which might be IPv6).
*
* @throws BadRequestException if request parameter named {@code name} is present but could not
* be parsed as an {@link InetAddress}
*/
public static Optional<InetAddress> extractOptionalInetAddressParameter(
HttpServletRequest req, String name) {
Optional<String> paramVal = extractOptionalParameter(req, name);
if (!paramVal.isPresent()) {
return Optional.absent();
}
try {
return Optional.of(InetAddresses.forString(paramVal.get()));
} catch (IllegalArgumentException e) {
throw new BadRequestException("Not an IPv4 or IPv6 address: " + name);
}
}
private static boolean equalsFalse(@Nullable String value) {
return nullToEmpty(value).equalsIgnoreCase("false");
}
/**
* Returns first HTTP header associated with {@code name}.
*
* @param name case insensitive header name
* @throws BadRequestException if request header is absent or empty
*/
public static String extractRequiredHeader(HttpServletRequest req, String name) {
String result = req.getHeader(name);
if (isNullOrEmpty(result)) {
throw new BadRequestException("Missing header: " + name);
}
return result;
}
private RequestParameters() {}
}

View file

@ -0,0 +1,28 @@
// 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.request;
import java.lang.annotation.Documented;
import javax.inject.Qualifier;
/**
* Dagger qualifier for the HTTP request path.
*
* @see javax.servlet.http.HttpServletRequest#getRequestURI()
*/
@Qualifier
@Documented
public @interface RequestPath {}

View file

@ -0,0 +1,30 @@
// 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.request;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.inject.Scope;
/** Dagger annotation for request-scoped components that depend on a global component. */
@Scope
@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestScope {}

View file

@ -0,0 +1,64 @@
// 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.request;
import com.google.common.net.MediaType;
import org.joda.time.DateTime;
import javax.servlet.http.HttpServletResponse;
/**
* HTTP request response object.
*
* @see ResponseImpl
*/
public interface Response {
/** Sets the HTTP status code. */
void setStatus(int status);
/** Sets the HTTP Content-Type and possibly encoding. */
void setContentType(MediaType contentType);
/**
* Writes the HTTP payload.
*
* @throws IllegalStateException if you've already written the payload
*/
void setPayload(String payload);
/**
* Writes an HTTP header to the response.
*
* @see HttpServletResponse#setHeader(String, String)
*/
void setHeader(String header, String value);
/**
* Writes an HTTP header with a timestamp value.
*
* @see HttpServletResponse#setDateHeader(String, long)
*/
void setDateHeader(String header, DateTime timestamp);
/**
* Sends a JavaScript redirect HTTP response.
*
* GAE handles a HTTP 302 status as an error, so using this is helpful for responses that might
* sometimes be consumed by GAE code, since it performs a redirect while also returning HTTP 200.
*/
void sendJavaScriptRedirect(String redirectUrl);
}

View file

@ -0,0 +1,73 @@
// 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.request;
import com.google.common.net.MediaType;
import org.joda.time.DateTime;
import java.io.IOException;
import javax.inject.Inject;
import javax.servlet.http.HttpServletResponse;
/** HTTP response object. */
public final class ResponseImpl implements Response {
/** Code for a JavaScript redirect. */
private static final String REDIRECT_PAYLOAD_FORMAT =
"<script>window.location.replace(\"%1$s\");</script><a href=\"%1$s\">%1$s</a>";
private final HttpServletResponse rsp;
@Inject
public ResponseImpl(HttpServletResponse rsp) {
this.rsp = rsp;
}
@Override
public void setStatus(int status) {
rsp.setStatus(status);
}
@Override
public void setContentType(MediaType contentType) {
rsp.setContentType(contentType.toString());
}
@Override
public void setPayload(String payload) {
try {
rsp.getWriter().write(payload);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void setHeader(String header, String value) {
rsp.setHeader(header, value);
}
@Override
public void setDateHeader(String header, DateTime timestamp) {
rsp.setDateHeader(header, timestamp.getMillis());
}
@Override
public void sendJavaScriptRedirect(String redirectUrl) {
setPayload(String.format(REDIRECT_PAYLOAD_FORMAT, redirectUrl));
}
}

View file

@ -0,0 +1,47 @@
// 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.request;
import com.google.auto.value.AutoValue;
import com.google.common.base.Function;
/**
* Mapping of an {@link Action} to a {@link Runnable} instantiator for request handling.
*
* @see Router
*/
@AutoValue
abstract class Route {
static Route create(Action action, Function<Object, Runnable> instantiator) {
return new AutoValue_Route(action, instantiator);
}
abstract Action action();
abstract Function<Object, Runnable> instantiator();
boolean isMethodAllowed(Action.Method requestMethod) {
for (Action.Method method : action().method()) {
if (method == requestMethod) {
return true;
}
}
return false;
}
boolean shouldXsrfProtect(Action.Method requestMethod) {
return action().xsrfProtection() && requestMethod != Action.Method.GET;
}
}

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.request;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Ordering;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.TreeMap;
/**
* Path prefix request router for Domain Registry.
*
* <p>See the documentation of {@link RequestHandler} for more information.
*
* <h3>Implementation Details</h3>
*
* <p>Request routing is O(logn) because {@link ImmutableSortedMap} performs a binary search over a
* contiguous array, which makes it faster than a {@link TreeMap}. However a prefix trie search in
* generated code would be the ideal approach.
*/
final class Router {
static Router create(Iterable<Method> componentMethods) {
return new Router(extractRoutesFromComponent(componentMethods));
}
private final ImmutableSortedMap<String, Route> routes;
private Router(ImmutableSortedMap<String, Route> routes) {
this.routes = routes;
}
/** Returns the appropriate action route for a request. */
Optional<Route> route(String path) {
Map.Entry<String, Route> floor = routes.floorEntry(path);
if (floor != null) {
if (floor.getValue().action().isPrefix()
? path.startsWith(floor.getKey())
: path.equals(floor.getKey())) {
return Optional.of(floor.getValue());
}
}
return Optional.absent();
}
private static
ImmutableSortedMap<String, Route> extractRoutesFromComponent(Iterable<Method> methods) {
ImmutableSortedMap.Builder<String, Route> routes =
new ImmutableSortedMap.Builder<>(Ordering.natural());
for (Method method : methods) {
if (!isDaggerInstantiatorOfType(Runnable.class, method)) {
continue;
}
Action action = method.getReturnType().getAnnotation(Action.class);
if (action == null) {
continue;
}
@SuppressWarnings("unchecked") // Safe due to previous checks.
Route route =
Route.create(action, (Function<Object, Runnable>) newInstantiator(method));
routes.put(action.path(), route);
}
return routes.build();
}
private static boolean isDaggerInstantiatorOfType(Class<?> type, Method method) {
return method.getParameterTypes().length == 0
&& type.isAssignableFrom(method.getReturnType());
}
private static Function<Object, ?> newInstantiator(final Method method) {
return new Function<Object, Object>() {
@Override
public Object apply(Object component) {
try {
return method.invoke(component);
} catch (IllegalAccessException e) {
throw new RuntimeException(
"Error reflectively accessing component's @Action factory method", e);
} catch (InvocationTargetException e) {
// This means an exception was thrown during the injection process while instantiating
// the @Action class; we should propagate that underlying exception.
Throwables.propagateIfPossible(e.getCause());
throw new AssertionError(
"Component's @Action factory method somehow threw checked exception", e);
}
}};
}
}

View file

@ -0,0 +1,51 @@
// 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.request;
import java.io.IOException;
import javax.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Action that delegates to a servlet.
*
* <p>This is a transitory solution for using legacy servlets inside the new injection action
* framework.
*
* @param <T> servlet type, which must have an {@link Inject @Inject} constructor
*/
public abstract class ServletDelegate<T extends HttpServlet> implements Runnable {
@Inject T servlet;
@Inject HttpServletRequest req;
@Inject HttpServletResponse rsp;
protected ServletDelegate() {}
@Override
public final void run() {
try {
servlet.init();
servlet.service(req, rsp);
servlet.destroy();
} catch (ServletException | IOException e) {
throw new RuntimeException(e);
}
}
}

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.request;