From 17d00807426627c0b86d026f291e3d624c7b4b29 Mon Sep 17 00:00:00 2001 From: Lai Jiang Date: Mon, 9 May 2022 17:17:05 -0400 Subject: [PATCH] Remove to-be-deprecated OOB OAuth flow in nomulus login (#1625) --- .../tools/ForwardingServerReceiver.java | 72 +++++++++++++++++++ .../google/registry/tools/LoginCommand.java | 31 +++++--- 2 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 core/src/main/java/google/registry/tools/ForwardingServerReceiver.java diff --git a/core/src/main/java/google/registry/tools/ForwardingServerReceiver.java b/core/src/main/java/google/registry/tools/ForwardingServerReceiver.java new file mode 100644 index 000000000..ac8df9640 --- /dev/null +++ b/core/src/main/java/google/registry/tools/ForwardingServerReceiver.java @@ -0,0 +1,72 @@ +// Copyright 2022 The Nomulus Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package google.registry.tools; + +import com.google.api.client.extensions.java6.auth.oauth2.VerificationCodeReceiver; +import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver; +import java.io.IOException; + +/** + * A thin wrapper around {@link LocalServerReceiver} which points the redirect URI to a different + * port (the forwarding port) while still listening on the random unused port (the remote port) + * nomulus itself picks. This allows us to run the nomulus tool on a remote host (which one can SSH + * into) while performing the OAuth 3-legged login flow in a local browser (from the lost host where + * the SSH client resides). + * + *

When performing the login flow, an HTTP server will be listening on the remote port and have a + * redirect_uri of http://localhost:remote_port, which is only accessible from the + * remote host. By changing the redirect_uri to http://localhost:forwarding_port, it + * becomes accessible from the local host, if local_host:forwarding_port is forwarded + * to remote_host:remote_port. + * + *

Note that port forwarding is required. We cannot use the remote host's IP or reverse + * DNS address in the redirect URI, even if they are directly accessible from the local host, + * because the only allowed redirect URI scheme for desktops apps when sending a request to the + * Google OAuth server is the loopback address with a port. + * + * @see + * redirect_uri values + */ +final class ForwardingServerReceiver implements VerificationCodeReceiver { + + private final int forwarding_port; + private final LocalServerReceiver localServerReceiver = new LocalServerReceiver(); + + ForwardingServerReceiver(int forwarding_port) { + this.forwarding_port = forwarding_port; + } + + @Override + public String getRedirectUri() throws IOException { + String redirect_uri = localServerReceiver.getRedirectUri(); + return redirect_uri.replace("localhost:" + getRemotePort(), "localhost:" + forwarding_port); + } + + @Override + public String waitForCode() throws IOException { + return localServerReceiver.waitForCode(); + } + + @Override + public void stop() throws IOException { + localServerReceiver.stop(); + System.out.println("You can now exit from the SSH session created for port forwarding."); + } + + int getRemotePort() { + return localServerReceiver.getPort(); + } +} diff --git a/core/src/main/java/google/registry/tools/LoginCommand.java b/core/src/main/java/google/registry/tools/LoginCommand.java index 3b4fce14f..10c9905b6 100644 --- a/core/src/main/java/google/registry/tools/LoginCommand.java +++ b/core/src/main/java/google/registry/tools/LoginCommand.java @@ -19,7 +19,7 @@ import com.beust.jcommander.Parameters; import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp; import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver; import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; -import com.google.api.client.googleapis.extensions.java6.auth.oauth2.GooglePromptReceiver; +import java.net.InetAddress; import javax.inject.Inject; /** Authorizes the nomulus tool for OAuth 2.0 access to remote resources. */ @@ -30,24 +30,35 @@ final class LoginCommand implements Command { @Inject @AuthModule.ClientScopeQualifier String clientScopeQualifier; @Parameter( - names = "--remote", + names = "--port", description = - "Whether the command is run on a remote host where access to a browser is not available. " - + "If set to true, a URL will be given and a code is expected to be entered after " - + "the user completes authorization by visiting that URL.") - private boolean remote = false; + "A free port on the local host. When set, it is assumed that the nomulus tool runs on a" + + " remote host whose browser is not accessible locally. i. e. if you SSH to a" + + " machine and run `nomulus` there, the ssh client is on the local host and nomulus" + + " runs on a remote host. You will need to forward the local port specified here to" + + " a remote port that nomulus randomly picks. Follow the instruction when prompted.") + private int port = 0; @Override public void run() throws Exception { AuthorizationCodeInstalledApp app; - if (remote) { + if (port != 0) { + String remote_host = InetAddress.getLocalHost().getHostName(); + ForwardingServerReceiver forwardingServerReceiver = new ForwardingServerReceiver(port); app = new AuthorizationCodeInstalledApp( flow, - new GooglePromptReceiver(), + forwardingServerReceiver, url -> { - System.out.println("Please open the following address in your browser:"); - System.out.println(" " + url); + int remote_port = forwardingServerReceiver.getRemotePort(); + System.out.printf( + "Please first run the following command in a separate terminal on your local " + + "host:\n\n ssh -L %s:localhost:%s %s\n\n", + port, remote_port, remote_host); + System.out.printf( + "Please then open the following URL in your local browser and follow the" + + " instructions:\n\n %s\n\n", + url); }); } else { app = new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver());