Change @Auth to an AutoValue, and created a set of predefined Auths

We want to be safer and more explicit about the authentication needed by the many actions that exist.

As such, we make the 'auth' parameter required in @Action (so it's always clear who can run a specific action) and we replace the @Auth with an enum so that only pre-approved configurations that are aptly named and documented can be used.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=162210306
This commit is contained in:
guyben 2017-07-17 07:34:17 -07:00 committed by Ben McIlwain
parent 5966d8077b
commit e224a67eda
94 changed files with 614 additions and 511 deletions

View file

@ -17,30 +17,23 @@ package google.registry.request;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.POST;
import static google.registry.request.auth.Auth.AUTH_INTERNAL_OR_ADMIN;
import static google.registry.request.auth.Auth.AUTH_PUBLIC;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import com.google.appengine.api.oauth.OAuthServiceFactory;
import com.google.appengine.api.users.User;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.base.Optional;
import com.google.common.testing.NullPointerTester;
import google.registry.request.HttpException.ServiceUnavailableException;
import google.registry.request.auth.AppEngineInternalAuthenticationMechanism;
import google.registry.request.auth.Auth;
import google.registry.request.auth.AuthLevel;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthenticationMechanism;
import google.registry.request.auth.LegacyAuthenticationMechanism;
import google.registry.request.auth.OAuthAuthenticationMechanism;
import google.registry.request.auth.RequestAuthenticator;
import google.registry.security.XsrfTokenManager;
import google.registry.request.auth.UserAuthInfo;
import google.registry.testing.AppEngineRule;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeUserService;
import google.registry.testing.Providers;
import google.registry.testing.UserInfo;
import java.io.PrintWriter;
@ -69,7 +62,7 @@ public final class RequestHandlerTest {
path = "/bumblebee",
method = {GET, POST},
isPrefix = true,
auth = @Auth(minimumLevel = AuthLevel.NONE)
auth = AUTH_PUBLIC
)
public static class BumblebeeTask implements Runnable {
@Override
@ -80,7 +73,7 @@ public final class RequestHandlerTest {
path = "/sloth",
method = POST,
automaticallyPrintOk = true,
auth = @Auth(minimumLevel = AuthLevel.NONE)
auth = AUTH_PUBLIC
)
public static class SlothTask implements Runnable {
@Override
@ -90,14 +83,14 @@ public final class RequestHandlerTest {
@Action(
path = "/safe-sloth",
method = {GET, POST},
auth = @Auth(minimumLevel = AuthLevel.NONE)
auth = AUTH_PUBLIC
)
public static class SafeSlothTask implements Runnable {
@Override
public void run() {}
}
@Action(path = "/fail", auth = @Auth(minimumLevel = AuthLevel.NONE))
@Action(path = "/fail", auth = AUTH_PUBLIC)
public static final class FailTask implements Runnable {
@Override
public void run() {
@ -105,7 +98,7 @@ public final class RequestHandlerTest {
}
}
@Action(path = "/failAtConstruction", auth = @Auth(minimumLevel = AuthLevel.NONE))
@Action(path = "/failAtConstruction", auth = AUTH_PUBLIC)
public static final class FailAtConstructionTask implements Runnable {
public FailAtConstructionTask() {
throw new ServiceUnavailableException("Fail at construction");
@ -132,7 +125,7 @@ public final class RequestHandlerTest {
@Action(
path = "/auth/none",
auth = @Auth(minimumLevel = AuthLevel.NONE),
auth = AUTH_PUBLIC,
method = Action.Method.GET
)
public class AuthNoneAction extends AuthBase {
@ -142,14 +135,11 @@ public final class RequestHandlerTest {
}
@Action(
path = "/auth/adminUserAnyMethod",
auth = @Auth(
methods = {Auth.AuthMethod.INTERNAL, Auth.AuthMethod.API, Auth.AuthMethod.LEGACY},
minimumLevel = AuthLevel.USER,
userPolicy = Auth.UserPolicy.ADMIN),
path = "/auth/adminUser",
auth = AUTH_INTERNAL_OR_ADMIN,
method = Action.Method.GET)
public class AuthAdminUserAnyMethodAction extends AuthBase {
AuthAdminUserAnyMethodAction(AuthResult authResult) {
public class AuthAdminUserAction extends AuthBase {
AuthAdminUserAction(AuthResult authResult) {
super(authResult);
}
}
@ -190,8 +180,8 @@ public final class RequestHandlerTest {
return new AuthNoneAction(component.getRequestModule().provideAuthResult());
}
public AuthAdminUserAnyMethodAction authAdminUserAnyMethodAction() {
return new AuthAdminUserAnyMethodAction(component.getRequestModule().provideAuthResult());
public AuthAdminUserAction authAdminUserAction() {
return new AuthAdminUserAction(component.getRequestModule().provideAuthResult());
}
}
@ -209,30 +199,16 @@ public final class RequestHandlerTest {
private final BumblebeeTask bumblebeeTask = mock(BumblebeeTask.class);
private final SlothTask slothTask = mock(SlothTask.class);
private final SafeSlothTask safeSlothTask = mock(SafeSlothTask.class);
private final RequestAuthenticator requestAuthenticator = mock(RequestAuthenticator.class);
private final Component component = new Component();
private final StringWriter httpOutput = new StringWriter();
private RequestHandler<Component> handler;
private AuthResult providedAuthResult = null;
private final User testUser = new User("test@example.com", "test@example.com");
private RequestAuthenticator requestAuthenticator;
private XsrfTokenManager xsrfTokenManager;
private FakeUserService userService;
@Before
public void before() throws Exception {
userService = new FakeUserService();
xsrfTokenManager = new XsrfTokenManager(new FakeClock(), userService);
requestAuthenticator = new RequestAuthenticator(
new AppEngineInternalAuthenticationMechanism(),
ImmutableList.<AuthenticationMechanism>of(
new OAuthAuthenticationMechanism(
OAuthServiceFactory.getOAuthService(),
ImmutableSet.of("https://www.googleapis.com/auth/userinfo.email"),
ImmutableSet.of("https://www.googleapis.com/auth/userinfo.email"),
ImmutableSet.of("proxy-client-id", "regtool-client-id"))),
new LegacyAuthenticationMechanism(userService, xsrfTokenManager));
// Initialize here, not inline, so that we pick up the mocked UserService.
handler = RequestHandler.<Component>createForTest(
Component.class,
@ -256,19 +232,24 @@ public final class RequestHandlerTest {
public void testHandleRequest_normalRequest_works() throws Exception {
when(req.getMethod()).thenReturn("GET");
when(req.getRequestURI()).thenReturn("/bumblebee");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
handler.handleRequest(req, rsp);
verifyZeroInteractions(rsp);
verify(bumblebeeTask).run();
}
@Test
public void testHandleRequest_multipleMethodMappings_works() throws Exception {
userService.setUser(testUser, false);
when(req.getMethod()).thenReturn("POST");
when(req.getHeader("X-CSRF-Token"))
.thenReturn(xsrfTokenManager.generateToken(testUser.getEmail()));
when(req.getRequestURI()).thenReturn("/bumblebee");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
handler.handleRequest(req, rsp);
verify(bumblebeeTask).run();
}
@ -276,18 +257,23 @@ public final class RequestHandlerTest {
public void testHandleRequest_prefixEnabled_subpathsWork() throws Exception {
when(req.getMethod()).thenReturn("GET");
when(req.getRequestURI()).thenReturn("/bumblebee/hive");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
handler.handleRequest(req, rsp);
verify(bumblebeeTask).run();
}
@Test
public void testHandleRequest_taskHasAutoPrintOk_printsOk() throws Exception {
userService.setUser(testUser, false);
when(req.getMethod()).thenReturn("POST");
when(req.getHeader("X-CSRF-Token"))
.thenReturn(xsrfTokenManager.generateToken(testUser.getEmail()));
when(req.getRequestURI()).thenReturn("/sloth");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
handler.handleRequest(req, rsp);
verify(slothTask).run();
verify(rsp).setContentType("text/plain; charset=utf-8");
verify(rsp).getWriter();
@ -298,7 +284,11 @@ public final class RequestHandlerTest {
public void testHandleRequest_prefixDisabled_subpathsReturn404NotFound() throws Exception {
when(req.getMethod()).thenReturn("POST");
when(req.getRequestURI()).thenReturn("/sloth/nest");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
handler.handleRequest(req, rsp);
verify(rsp).sendError(404);
}
@ -306,7 +296,11 @@ public final class RequestHandlerTest {
public void testHandleRequest_taskThrowsHttpException_getsHandledByHandler() throws Exception {
when(req.getMethod()).thenReturn("GET");
when(req.getRequestURI()).thenReturn("/fail");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
handler.handleRequest(req, rsp);
verify(rsp).sendError(503, "Set sail for fail");
}
@ -316,7 +310,11 @@ public final class RequestHandlerTest {
throws Exception {
when(req.getMethod()).thenReturn("GET");
when(req.getRequestURI()).thenReturn("/failAtConstruction");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
handler.handleRequest(req, rsp);
verify(rsp).sendError(503, "Fail at construction");
}
@ -324,7 +322,11 @@ public final class RequestHandlerTest {
public void testHandleRequest_notFound_returns404NotFound() throws Exception {
when(req.getMethod()).thenReturn("GET");
when(req.getRequestURI()).thenReturn("/bogus");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
handler.handleRequest(req, rsp);
verify(rsp).sendError(404);
}
@ -332,7 +334,11 @@ public final class RequestHandlerTest {
public void testHandleRequest_methodNotAllowed_returns405MethodNotAllowed() throws Exception {
when(req.getMethod()).thenReturn("POST");
when(req.getRequestURI()).thenReturn("/fail");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
handler.handleRequest(req, rsp);
verify(rsp).sendError(405);
}
@ -340,7 +346,11 @@ public final class RequestHandlerTest {
public void testHandleRequest_insaneMethod_returns405MethodNotAllowed() throws Exception {
when(req.getMethod()).thenReturn("FIREAWAY");
when(req.getRequestURI()).thenReturn("/fail");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
handler.handleRequest(req, rsp);
verify(rsp).sendError(405);
}
@ -350,7 +360,11 @@ public final class RequestHandlerTest {
public void testHandleRequest_lowercaseMethod_notRecognized() throws Exception {
when(req.getMethod()).thenReturn("get");
when(req.getRequestURI()).thenReturn("/bumblebee");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
handler.handleRequest(req, rsp);
verify(rsp).sendError(405);
}
@ -359,19 +373,19 @@ public final class RequestHandlerTest {
NullPointerTester tester = new NullPointerTester();
tester.setDefault(Class.class, Component.class);
tester.setDefault(RequestAuthenticator.class, requestAuthenticator);
tester.setDefault(XsrfTokenManager.class, xsrfTokenManager);
tester.testAllPublicStaticMethods(RequestHandler.class);
tester.testAllPublicInstanceMethods(handler);
}
@Test
public void testXsrfProtection_validTokenProvided_runsAction() throws Exception {
userService.setUser(testUser, false);
when(req.getMethod()).thenReturn("POST");
when(req.getHeader("X-CSRF-Token"))
.thenReturn(xsrfTokenManager.generateToken(testUser.getEmail()));
when(req.getRequestURI()).thenReturn("/safe-sloth");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
handler.handleRequest(req, rsp);
verify(safeSlothTask).run();
}
@ -379,7 +393,11 @@ public final class RequestHandlerTest {
public void testXsrfProtection_GETMethodWithoutToken_doesntCheckToken() throws Exception {
when(req.getMethod()).thenReturn("GET");
when(req.getRequestURI()).thenReturn("/safe-sloth");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
handler.handleRequest(req, rsp);
verify(safeSlothTask).run();
}
@ -387,6 +405,8 @@ public final class RequestHandlerTest {
public void testNoAuthNeeded_success() throws Exception {
when(req.getMethod()).thenReturn("GET");
when(req.getRequestURI()).thenReturn("/auth/none");
when(requestAuthenticator.authorize(AUTH_PUBLIC.authSettings(), req))
.thenReturn(Optional.of(AuthResult.create(AuthLevel.NONE)));
handler.handleRequest(req, rsp);
@ -396,22 +416,11 @@ public final class RequestHandlerTest {
}
@Test
public void testAuthNeeded_notLoggedIn() throws Exception {
public void testAuthNeeded_failure() throws Exception {
when(req.getMethod()).thenReturn("GET");
when(req.getRequestURI()).thenReturn("/auth/adminUserAnyMethod");
handler.handleRequest(req, rsp);
verify(rsp).sendError(403, "Not authorized");
assertThat(providedAuthResult).isNull();
assertThat(providedAuthResult).isNull();
}
@Test
public void testAuthNeeded_notAuthorized() throws Exception {
userService.setUser(testUser, false);
when(req.getMethod()).thenReturn("GET");
when(req.getRequestURI()).thenReturn("/auth/adminUserAnyMethod");
when(req.getRequestURI()).thenReturn("/auth/adminUser");
when(requestAuthenticator.authorize(AUTH_INTERNAL_OR_ADMIN.authSettings(), req))
.thenReturn(Optional.absent());
handler.handleRequest(req, rsp);
@ -421,9 +430,11 @@ public final class RequestHandlerTest {
@Test
public void testAuthNeeded_success() throws Exception {
userService.setUser(testUser, true);
when(req.getMethod()).thenReturn("GET");
when(req.getRequestURI()).thenReturn("/auth/adminUserAnyMethod");
when(req.getRequestURI()).thenReturn("/auth/adminUser");
when(requestAuthenticator.authorize(AUTH_INTERNAL_OR_ADMIN.authSettings(), req))
.thenReturn(
Optional.of(AuthResult.create(AuthLevel.USER, UserAuthInfo.create(testUser, true))));
handler.handleRequest(req, rsp);
@ -433,4 +444,5 @@ public final class RequestHandlerTest {
assertThat(providedAuthResult.userAuthInfo().get().user()).isEqualTo(testUser);
assertThat(providedAuthResult.userAuthInfo().get().oauthTokenInfo()).isAbsent();
}
}