diff --git a/ipn/backend.go b/ipn/backend.go index 29f7daaaf..06c8966e3 100644 --- a/ipn/backend.go +++ b/ipn/backend.go @@ -8,6 +8,7 @@ import ( "net/http" "time" + "golang.org/x/oauth2" "tailscale.com/control/controlclient" "tailscale.com/ipn/ipnstate" "tailscale.com/tailcfg" @@ -27,6 +28,10 @@ const ( Running ) +// GoogleIDToken Type is the oauth2.Token.TokenType for the Google +// ID tokens used by the Android client. +const GoogleIDTokenType = "ts_android_google_login" + func (s State) String() string { return [...]string{"NoState", "NeedsLogin", "NeedsMachineAuth", "Stopped", "Starting", "Running"}[s] @@ -129,6 +134,8 @@ type Backend interface { // flow. This should trigger a new BrowseToURL notification // eventually. StartLoginInteractive() + // Login logs in with an OAuth2 token. + Login(token *oauth2.Token) // Logout terminates the current login session and stops the // wireguard engine. Logout() diff --git a/ipn/fake_test.go b/ipn/fake_test.go index 312a75e1c..1b8605534 100644 --- a/ipn/fake_test.go +++ b/ipn/fake_test.go @@ -8,6 +8,7 @@ import ( "log" "time" + "golang.org/x/oauth2" "tailscale.com/control/controlclient" "tailscale.com/ipn/ipnstate" ) @@ -42,6 +43,14 @@ func (b *FakeBackend) newState(s State) { func (b *FakeBackend) StartLoginInteractive() { u := b.serverURL + "/this/is/fake" b.notify(Notify{BrowseToURL: &u}) + b.login() +} + +func (b *FakeBackend) Login(token *oauth2.Token) { + b.login() +} + +func (b *FakeBackend) login() { b.newState(NeedsMachineAuth) b.newState(Stopped) // TODO(apenwarr): Fill in a more interesting netmap here. diff --git a/ipn/handle.go b/ipn/handle.go index e8d43096f..c9f761b4c 100644 --- a/ipn/handle.go +++ b/ipn/handle.go @@ -9,6 +9,7 @@ import ( "time" "github.com/tailscale/wireguard-go/wgcfg" + "golang.org/x/oauth2" "tailscale.com/control/controlclient" "tailscale.com/types/logger" ) @@ -154,6 +155,10 @@ func (h *Handle) StartLoginInteractive() { h.b.StartLoginInteractive() } +func (h *Handle) Login(token *oauth2.Token) { + h.b.Login(token) +} + func (h *Handle) Logout() { h.b.Logout() } diff --git a/ipn/local.go b/ipn/local.go index 370732b5f..bbc52be69 100644 --- a/ipn/local.go +++ b/ipn/local.go @@ -13,6 +13,7 @@ import ( "time" "github.com/tailscale/wireguard-go/wgcfg" + "golang.org/x/oauth2" "inet.af/netaddr" "tailscale.com/control/controlclient" "tailscale.com/ipn/ipnstate" @@ -612,6 +613,16 @@ func (b *LocalBackend) getEngineStatus() EngineStatus { return b.engineStatus } +// Login implements Backend. +func (b *LocalBackend) Login(token *oauth2.Token) { + b.mu.Lock() + b.assertClientLocked() + c := b.c + b.mu.Unlock() + + c.Login(token, controlclient.LoginInteractive) +} + // StartLoginInteractive implements Backend. It requests a new // interactive login from controlclient, unless such a flow is already // in progress, in which case StartLoginInteractive attempts to pick diff --git a/ipn/message.go b/ipn/message.go index cac0eb07f..6323bb8c3 100644 --- a/ipn/message.go +++ b/ipn/message.go @@ -13,6 +13,7 @@ import ( "log" "time" + "golang.org/x/oauth2" "tailscale.com/types/logger" "tailscale.com/types/structs" "tailscale.com/version" @@ -49,6 +50,7 @@ type Command struct { Quit *NoArgs Start *StartArgs StartLoginInteractive *NoArgs + Login *oauth2.Token Logout *NoArgs SetPrefs *SetPrefsArgs RequestEngineStatus *NoArgs @@ -128,6 +130,9 @@ func (bs *BackendServer) GotCommand(cmd *Command) error { } else if c := cmd.StartLoginInteractive; c != nil { bs.b.StartLoginInteractive() return nil + } else if c := cmd.Login; c != nil { + bs.b.Login(c) + return nil } else if c := cmd.Logout; c != nil { bs.b.Logout() return nil @@ -225,6 +230,10 @@ func (bc *BackendClient) StartLoginInteractive() { bc.send(Command{StartLoginInteractive: &NoArgs{}}) } +func (bc *BackendClient) Login(token *oauth2.Token) { + bc.send(Command{Login: token}) +} + func (bc *BackendClient) Logout() { bc.send(Command{Logout: &NoArgs{}}) } diff --git a/ipn/message_test.go b/ipn/message_test.go index 4b0ebd9f5..1a657bc2f 100644 --- a/ipn/message_test.go +++ b/ipn/message_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "golang.org/x/oauth2" "tailscale.com/tstest" ) @@ -177,4 +178,10 @@ func TestClientServer(t *testing.T) { h.Logout() flushUntil(NeedsLogin) + + h.Login(&oauth2.Token{ + AccessToken: "google_id_token", + TokenType: GoogleIDTokenType, + }) + flushUntil(Running) }