Repository: dexidp/dex
Pull request #1622 (2020-01-13 07:24:54)

connector/microsoft: Add basic tests


Patch:
diff --git a/connector/microsoft/microsoft.go b/connector/microsoft/microsoft.go
index d4ce2e67..142a7c6c 100644
--- a/connector/microsoft/microsoft.go
+++ b/connector/microsoft/microsoft.go
@@ -31,7 +31,6 @@ const (
 )
 
 const (
-	apiURL = "https://graph.microsoft.com"
 	// Microsoft requires this scope to access user's profile
 	scopeUser = "user.read"
 	// Microsoft requires this scope to list groups the user is a member of
@@ -54,6 +53,8 @@ type Config struct {
 // Open returns a strategy for logging in through Microsoft.
 func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) {
 	m := microsoftConnector{
+		apiURL:               "https://login.microsoftonline.com",
+		graphURL:             "https://graph.microsoft.com",
 		redirectURI:          c.RedirectURI,
 		clientID:             c.ClientID,
 		clientSecret:         c.ClientSecret,
@@ -94,6 +95,8 @@ var (
 )
 
 type microsoftConnector struct {
+	apiURL               string
+	graphURL             string
 	redirectURI          string
 	clientID             string
 	clientSecret         string
@@ -123,8 +126,8 @@ func (c *microsoftConnector) oauth2Config(scopes connector.Scopes) *oauth2.Confi
 		ClientID:     c.clientID,
 		ClientSecret: c.clientSecret,
 		Endpoint: oauth2.Endpoint{
-			AuthURL:  "https://login.microsoftonline.com/" + c.tenant + "/oauth2/v2.0/authorize",
-			TokenURL: "https://login.microsoftonline.com/" + c.tenant + "/oauth2/v2.0/token",
+			AuthURL:  c.apiURL + "/" + c.tenant + "/oauth2/v2.0/authorize",
+			TokenURL: c.apiURL + "/" + c.tenant + "/oauth2/v2.0/token",
 		},
 		Scopes:      microsoftScopes,
 		RedirectURL: c.redirectURI,
@@ -296,7 +299,7 @@ type user struct {
 
 func (c *microsoftConnector) user(ctx context.Context, client *http.Client) (u user, err error) {
 	// https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_get
-	req, err := http.NewRequest("GET", apiURL+"/v1.0/me?$select=id,displayName,userPrincipalName", nil)
+	req, err := http.NewRequest("GET", c.graphURL+"/v1.0/me?$select=id,displayName,userPrincipalName", nil)
 	if err != nil {
 		return u, fmt.Errorf("new req: %v", err)
 	}
@@ -355,7 +358,7 @@ func (c *microsoftConnector) getGroupIDs(ctx context.Context, client *http.Clien
 	in := &struct {
 		SecurityEnabledOnly bool `json:"securityEnabledOnly"`
 	}{c.onlySecurityGroups}
-	reqURL := apiURL + "/v1.0/me/getMemberGroups"
+	reqURL := c.graphURL + "/v1.0/me/getMemberGroups"
 	for {
 		var out []string
 		var next string
@@ -383,7 +386,7 @@ func (c *microsoftConnector) getGroupNames(ctx context.Context, client *http.Cli
 		IDs   []string `json:"ids"`
 		Types []string `json:"types"`
 	}{ids, []string{"group"}}
-	reqURL := apiURL + "/v1.0/directoryObjects/getByIds"
+	reqURL := c.graphURL + "/v1.0/directoryObjects/getByIds"
 	for {
 		var out []group
 		var next string


Test patch:
diff --git a/connector/microsoft/microsoft_test.go b/connector/microsoft/microsoft_test.go
new file mode 100644
index 00000000..3fba9c2a
--- /dev/null
+++ b/connector/microsoft/microsoft_test.go
@@ -0,0 +1,90 @@
+package microsoft
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+	"os"
+	"reflect"
+	"testing"
+
+	"github.com/dexidp/dex/connector"
+)
+
+type testResponse struct {
+	data interface{}
+}
+
+const tenant = "9b1c3439-a67e-4e92-bb0d-0571d44ca965"
+
+var dummyToken = testResponse{data: map[string]interface{}{
+	"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9",
+	"expires_in":   "30",
+}}
+
+func TestUserIdentityFromGraphAPI(t *testing.T) {
+	s := newTestServer(map[string]testResponse{
+		"/v1.0/me?$select=id,displayName,userPrincipalName": {
+			data: user{ID: "S56767889", Name: "Jane Doe", Email: "jane.doe@example.com"},
+		},
+		"/" + tenant + "/oauth2/v2.0/token": dummyToken,
+	})
+	defer s.Close()
+
+	req, _ := http.NewRequest("GET", s.URL, nil)
+
+	c := microsoftConnector{apiURL: s.URL, graphURL: s.URL, tenant: tenant}
+	identity, err := c.HandleCallback(connector.Scopes{Groups: false}, req)
+	expectNil(t, err)
+	expectEquals(t, identity.Username, "Jane Doe")
+	expectEquals(t, identity.UserID, "S56767889")
+	expectEquals(t, identity.PreferredUsername, "")
+	expectEquals(t, identity.Email, "jane.doe@example.com")
+	expectEquals(t, identity.EmailVerified, true)
+	expectEquals(t, len(identity.Groups), 0)
+}
+
+func TestUserGroupsFromGraphAPI(t *testing.T) {
+	s := newTestServer(map[string]testResponse{
+		"/v1.0/me?$select=id,displayName,userPrincipalName": {data: user{}},
+		"/v1.0/me/getMemberGroups": {data: map[string]interface{}{
+			"value": []string{"a", "b"},
+		}},
+		"/" + tenant + "/oauth2/v2.0/token": dummyToken,
+	})
+	defer s.Close()
+
+	req, _ := http.NewRequest("GET", s.URL, nil)
+
+	c := microsoftConnector{apiURL: s.URL, graphURL: s.URL, tenant: tenant}
+	identity, err := c.HandleCallback(connector.Scopes{Groups: true}, req)
+	expectNil(t, err)
+	expectEquals(t, identity.Groups, []string{"a", "b"})
+}
+
+func newTestServer(responses map[string]testResponse) *httptest.Server {
+	s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		response, found := responses[r.RequestURI]
+		if !found {
+			fmt.Fprintf(os.Stderr, "Mock response for %q not found\n", r.RequestURI)
+			http.NotFound(w, r)
+			return
+		}
+		w.Header().Add("Content-Type", "application/json")
+		json.NewEncoder(w).Encode(response.data)
+	}))
+	return s
+}
+
+func expectNil(t *testing.T, a interface{}) {
+	if a != nil {
+		t.Errorf("Expected %+v to equal nil", a)
+	}
+}
+
+func expectEquals(t *testing.T, a interface{}, b interface{}) {
+	if !reflect.DeepEqual(a, b) {
+		t.Errorf("Expected %+v to equal %+v", a, b)
+	}
+}