Merge pull request #2011 from molecula/paginate

[FB-1303] Iterate through group membership http response
This commit is contained in:
Samir Patel 2022-04-11 15:26:37 -04:00 committed by GitHub
commit be17087a68
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 123 additions and 46 deletions

View file

@ -52,7 +52,8 @@ type Group struct {
// Groups holds a slice of Group for marshalling from JSON
type Groups struct {
Groups []Group `json:"value"`
NextLink string `json:"@odata.nextLink"`
Groups []Group `json:"value"`
}
// Auth holds state, configuration, and utilities needed for authentication.
@ -128,6 +129,9 @@ func (a *Auth) Authenticate(ctx context.Context, bearer string) (*UserInfo, erro
if err != nil {
return nil, errors.Wrap(err, "refreshing token")
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("refreshing token: %s", resp.Status)
}
defer resp.Body.Close()
var t oauth2.Token
if err := json.NewDecoder(resp.Body).Decode(&t); err != nil {
@ -240,25 +244,39 @@ func (a *Auth) Redirect(w http.ResponseWriter, r *http.Request) {
func (a *Auth) getGroups(token string) ([]Group, error) {
var groups Groups
g, ok := a.groupsCache[token]
if ok && (time.Now().Sub(g.cacheTime) < a.cacheTTL) {
return g.groups, nil
gc, ok := a.groupsCache[token]
if ok && (time.Now().Sub(gc.cacheTime) < a.cacheTTL) && len(gc.groups) > 0 {
return gc.groups, nil
}
req, err := http.NewRequest("GET", a.groupEndpoint, nil)
if err != nil {
return groups.Groups, errors.Wrap(err, "creating new request to group endpoint")
nextLink := a.groupEndpoint
for nextLink != "" {
req, err := http.NewRequest("GET", nextLink, nil)
if err != nil {
return nil, errors.Wrap(err, "creating new request to group endpoint")
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
response, err := http.DefaultClient.Do(req)
if err != nil {
return nil, errors.Wrap(err, "getting group membership info")
}
if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("getting group membership info: %s", response.Status)
}
var g Groups
if err = json.NewDecoder(response.Body).Decode(&g); err != nil {
return groups.Groups, errors.Wrap(err, "failed unmarshalling group membership response")
}
response.Body.Close()
groups.Groups = append(groups.Groups, g.Groups...)
nextLink = g.NextLink
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
response, err := http.DefaultClient.Do(req)
if err != nil {
return groups.Groups, errors.Wrap(err, "getting group membership info")
}
defer response.Body.Close()
if err = json.NewDecoder(response.Body).Decode(&groups); err != nil {
return groups.Groups, errors.Wrap(err, "failed unmarshalling group membership response")
if len(groups.Groups) == 0 {
return nil, fmt.Errorf("no groups found")
}
a.groupsCache[token] = cachedGroups{

View file

@ -198,7 +198,7 @@ func TestAuthenticate(t *testing.T) {
refresh: true,
errOnRefresh: true,
exp: -17764800,
err: fmt.Errorf("decoding refreshed token: invalid character 'b' looking for beginning of value"),
err: fmt.Errorf("refreshing token: 500 Internal Server Error"),
},
}
for _, test := range cases {
@ -352,19 +352,19 @@ func TestGetGroups(t *testing.T) {
cacheTime: time.Now(),
groups: []Group{
{
GroupID: "i feel it in the water",
GroupName: "i feel it in the earth",
GroupID: "a han noston ned wilith",
GroupName: "I smell it in the air",
},
},
},
}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
srvNext := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, err := json.Marshal(
Groups{
Groups: []Group{
{
GroupID: "much that once was is lost",
GroupName: "for none now live who remember it",
GroupID: "han mathon ne chae",
GroupName: "I feel it in the earth",
},
},
},
@ -374,6 +374,25 @@ func TestGetGroups(t *testing.T) {
}
fmt.Fprintf(w, "%s", body)
}))
defer srvNext.Close()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, err := json.Marshal(
Groups{
NextLink: srvNext.URL,
Groups: []Group{
{
GroupID: "han mathon ne nen",
GroupName: "i feel it in the water",
},
},
},
)
if err != nil {
t.Fatalf("unexpected error marshalling groups response: %v", err)
}
fmt.Fprintf(w, "%s", body)
}))
defer srv.Close()
a.groupEndpoint = srv.URL
for name, test := range map[string]struct {
@ -384,8 +403,8 @@ func TestGetGroups(t *testing.T) {
token: "the world is changed",
groups: []Group{
{
GroupID: "i feel it in the water",
GroupName: "i feel it in the earth",
GroupID: "a han noston ned wilith",
GroupName: "I smell it in the air",
},
},
},
@ -393,8 +412,12 @@ func TestGetGroups(t *testing.T) {
token: "i smell it in the air",
groups: []Group{
{
GroupID: "much that once was is lost",
GroupName: "for none now live who remember it",
GroupID: "han mathon ne nen",
GroupName: "i feel it in the water",
},
{
GroupID: "han mathon ne chae",
GroupName: "I feel it in the earth",
},
},
},

View file

@ -5,6 +5,7 @@ import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
@ -190,12 +191,42 @@ func readResponse(w *httptest.ResponseRecorder) ([]byte, error) {
func TestAuthentication(t *testing.T) {
type evaluate func(w *httptest.ResponseRecorder, data []byte)
type endpoint func(w http.ResponseWriter, r *http.Request)
type Group struct {
GroupID string `json:"id"`
GroupName string `json:"displayName"`
}
// Groups holds a slice of Group for marshalling from JSON
type Groups struct {
NextLink string `json:"@odata.nextLink"`
Groups []Group `json:"value"`
}
groupSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, err := json.Marshal(
Groups{
Groups: []Group{
{
GroupID: "what are you?",
GroupName: "i am a carbon-based bipedal life form descended from an ape",
},
},
},
)
if err != nil {
t.Fatalf("unexpected error marshalling groups response: %v", err)
}
fmt.Fprintf(w, "%s", body)
}))
defer groupSrv.Close()
var (
ClientId = "e9088663-eb08-41d7-8f65-efb5f54bbb71"
ClientSecret = "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
AuthorizeURL = "https://login.microsoftonline.com/4a137d66-d161-4ae4-b1e6-07e9920874b8/oauth2/v2.0/authorize"
TokenURL = "https://login.microsoftonline.com/4a137d66-d161-4ae4-b1e6-07e9920874b8/oauth2/v2.0/token"
GroupEndpointURL = "https://graph.microsoft.com/v1.0/me/transitiveMemberOf/microsoft.graph.group?$count=true"
GroupEndpointURL = groupSrv.URL
LogoutURL = "https://login.microsoftonline.com/common/oauth2/v2.0/logout"
Scopes = []string{"https://graph.microsoft.com/.default", "offline_access"}
SecretKey = "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
@ -650,29 +681,29 @@ func TestChkAuthN(t *testing.T) {
}
cases := []struct {
name string
endpoint string
token string
handler http.HandlerFunc
statusCode int
name string
endpoint string
token string
handler http.HandlerFunc
err string
}{
{
name: "Valid",
token: validToken,
handler: h.chkAuthN(testingHandler),
statusCode: http.StatusOK,
name: "ValidToken-ButNotForMicrosoft",
token: validToken,
handler: h.chkAuthN(testingHandler),
err: "authenticating: getting groups: getting group membership info",
},
{
name: "Invalid",
token: invalidToken,
handler: h.chkAuthN(testingHandler),
statusCode: http.StatusUnauthorized,
name: "Invalid",
token: invalidToken,
handler: h.chkAuthN(testingHandler),
err: "authenticating: parsing bearer token",
},
{
name: "Expired",
token: expiredToken,
handler: h.chkAuthN(testingHandler),
statusCode: http.StatusUnauthorized,
name: "Expired",
token: expiredToken,
handler: h.chkAuthN(testingHandler),
err: "authenticating: token is expired",
},
}
for _, test := range cases {
@ -682,8 +713,13 @@ func TestChkAuthN(t *testing.T) {
r.Header.Add("Authorization", test.token)
test.handler(w, r)
resp := w.Result()
if resp.StatusCode != test.statusCode {
t.Fatalf("expected %v, got %v", test.statusCode, resp.StatusCode)
body, err := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(string(body), test.err) {
t.Fatalf("expected error %s, got: %s", test.err, string(body))
}
})
}