From 7d2ffed0d12c6f0bf0649c1237fd9224f02c2df8 Mon Sep 17 00:00:00 2001
From: Niall Sheridan <nsheridan@gmail.com>
Date: Wed, 20 Jun 2018 22:41:12 +0100
Subject: [PATCH] WIP: Add Microsoft Azure oauth support

First pass at adding MS Azure OAuth2. This works (an access token is issued) but needs way mor work.

Cashier configuration:
oauth_client_id: This is the Application ID assigned when you register the app in Azure AD
oauth_client_secret: This is created when you create a new key in the application
provider_opts: Set "tenant" to an AD domain name (e.g. blabbedy.onmicrosoft.com)

TODO:
Users whitelist is untested currently.
Valid() is unimplemented.
Revoke() is unimplemented. Not checked if an API exists on the MS side.
Email() is unimplemented. Should be pretty simple using the graph API.
Needs tests.

Oh and last but by no means least MS issue INSANELY LONG (> 1800 chars!) access tokens which are too long for my shell. This represents a significant issue :/
---
 server/auth/microsoft/microsoft.go            | 110 ++++++++++++++++++
 .../x/oauth2/microsoft/microsoft.go           |  31 +++++
 vendor/vendor.json                            |   6 +
 3 files changed, 147 insertions(+)
 create mode 100644 server/auth/microsoft/microsoft.go
 create mode 100644 vendor/golang.org/x/oauth2/microsoft/microsoft.go

diff --git a/server/auth/microsoft/microsoft.go b/server/auth/microsoft/microsoft.go
new file mode 100644
index 00000000..fa2028e2
--- /dev/null
+++ b/server/auth/microsoft/microsoft.go
@@ -0,0 +1,110 @@
+package microsoft
+
+import (
+	"errors"
+	"net/http"
+	"strings"
+
+	"github.com/nsheridan/cashier/server/auth"
+	"github.com/nsheridan/cashier/server/config"
+	"github.com/nsheridan/cashier/server/metrics"
+
+	"golang.org/x/oauth2"
+	"golang.org/x/oauth2/microsoft"
+)
+
+//XXX: This is a work-in-progress! DO NOT USE!
+// MS issue INSANELY LONG access tokens that exceed shell line length
+
+const (
+	//FIXME:
+	// revokeURL = ""
+	name = "microsoft"
+)
+
+// Config is an implementation of `auth.Provider` for authenticating using a
+// Microsoft Azure AD account.
+type Config struct {
+	config    *oauth2.Config
+	tenant    string
+	whitelist map[string]bool
+}
+
+var _ auth.Provider = (*Config)(nil)
+
+// New creates a new Google provider from a configuration.
+func New(c *config.Auth) (*Config, error) {
+	uw := make(map[string]bool)
+	for _, u := range c.UsersWhitelist {
+		uw[u] = true
+	}
+	if c.ProviderOpts["tenant"] == "" && len(uw) == 0 {
+		return nil, errors.New("either AD tenant or users whitelist must be specified")
+	}
+
+	return &Config{
+		config: &oauth2.Config{
+			ClientID:     c.OauthClientID,
+			ClientSecret: c.OauthClientSecret,
+			RedirectURL:  c.OauthCallbackURL,
+			Endpoint:     microsoft.AzureADEndpoint(c.ProviderOpts["tenant"]),
+			Scopes:       []string{"user.Read"},
+		},
+		whitelist: uw,
+	}, nil
+}
+
+// A new oauth2 http client.
+func (c *Config) newClient(token *oauth2.Token) *http.Client {
+	return c.config.Client(oauth2.NoContext, token)
+}
+
+// Name returns the name of the provider.
+func (c *Config) Name() string {
+	return name
+}
+
+// Valid validates the oauth token.
+func (c *Config) Valid(token *oauth2.Token) bool {
+	// FIXME: Validate harder
+	if !token.Valid() {
+		return false
+	}
+	metrics.M.AuthValid.WithLabelValues("microsoft").Inc()
+	return true
+}
+
+// Revoke disables the access token.
+func (c *Config) Revoke(token *oauth2.Token) error {
+	//FIXME
+	return nil
+}
+
+// StartSession retrieves an authentication endpoint from Google.
+func (c *Config) StartSession(state string) *auth.Session {
+	return &auth.Session{
+		AuthURL: c.config.AuthCodeURL(state),
+	}
+}
+
+// Exchange authorizes the session and returns an access token.
+func (c *Config) Exchange(code string) (*oauth2.Token, error) {
+	t, err := c.config.Exchange(oauth2.NoContext, code)
+	if err == nil {
+		metrics.M.AuthExchange.WithLabelValues("google").Inc()
+	}
+	return t, err
+}
+
+// Email retrieves the email address of the user.
+func (c *Config) Email(token *oauth2.Token) string {
+	// TODO: Given an access token, request the current user profile from
+	// `https://graph.microsoft.com/v1.0/me`. Parse out whatever passes for an
+	// email address from the response.
+	return ""
+}
+
+// Username retrieves the username portion of the user's email address.
+func (c *Config) Username(token *oauth2.Token) string {
+	return strings.Split(c.Email(token), "@")[0]
+}
diff --git a/vendor/golang.org/x/oauth2/microsoft/microsoft.go b/vendor/golang.org/x/oauth2/microsoft/microsoft.go
new file mode 100644
index 00000000..3ffbc57a
--- /dev/null
+++ b/vendor/golang.org/x/oauth2/microsoft/microsoft.go
@@ -0,0 +1,31 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package microsoft provides constants for using OAuth2 to access Windows Live ID.
+package microsoft // import "golang.org/x/oauth2/microsoft"
+
+import (
+	"golang.org/x/oauth2"
+)
+
+// LiveConnectEndpoint is Windows's Live ID OAuth 2.0 endpoint.
+var LiveConnectEndpoint = oauth2.Endpoint{
+	AuthURL:  "https://login.live.com/oauth20_authorize.srf",
+	TokenURL: "https://login.live.com/oauth20_token.srf",
+}
+
+// AzureADEndpoint returns a new oauth2.Endpoint for the given tenant at Azure Active Directory.
+// If tenant is empty, it uses the tenant called `common`.
+//
+// For more information see:
+// https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols#endpoints
+func AzureADEndpoint(tenant string) oauth2.Endpoint {
+	if tenant == "" {
+		tenant = "common"
+	}
+	return oauth2.Endpoint{
+		AuthURL:  "https://login.microsoftonline.com/" + tenant + "/oauth2/v2.0/authorize",
+		TokenURL: "https://login.microsoftonline.com/" + tenant + "/oauth2/v2.0/token",
+	}
+}
diff --git a/vendor/vendor.json b/vendor/vendor.json
index 01e666ce..0f7a2bab 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -958,6 +958,12 @@
 			"revision": "ef147856a6ddbb60760db74283d2424e98c87bff",
 			"revisionTime": "2018-06-20T17:47:24Z"
 		},
+		{
+			"checksumSHA1": "91mzAbqHQ6AAK65DzB4IkLOcvtk=",
+			"path": "golang.org/x/oauth2/microsoft",
+			"revision": "ef147856a6ddbb60760db74283d2424e98c87bff",
+			"revisionTime": "2018-06-20T17:47:24Z"
+		},
 		{
 			"checksumSHA1": "USMsFbKZrNd1b9oRFbjevPr6PmI=",
 			"path": "golang.org/x/sys/unix",
-- 
GitLab