Skip to content

Commit

Permalink
PXP-11248 PXP-11258 "POST /auth/mapping" anonymous support (#164)
Browse files Browse the repository at this point in the history
  • Loading branch information
paulineribeyre authored Mar 12, 2024
1 parent 2904a31 commit 936ef1b
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 39 deletions.
4 changes: 2 additions & 2 deletions .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"files": "go.sum|^.secrets.baseline$",
"lines": null
},
"generated_at": "2024-03-01T20:51:33Z",
"generated_at": "2024-03-11T21:41:11Z",
"plugins_used": [
{
"name": "AWSKeyDetector"
Expand Down Expand Up @@ -70,7 +70,7 @@
{
"hashed_secret": "f9fdc64928c96c7ad56bf7da557f70345d83a6ed",
"is_verified": false,
"line_number": 1684,
"line_number": 1688,
"type": "Base64 High Entropy String"
}
],
Expand Down
49 changes: 36 additions & 13 deletions arborist/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,28 +176,34 @@ func (server *Server) MakeRouter(out io.Writer) http.Handler {
// handler signature.
func (server *Server) parseJSON(baseHandler func(http.ResponseWriter, *http.Request, []byte)) func(http.ResponseWriter, *http.Request) {
handler := func(w http.ResponseWriter, r *http.Request) {
body := server.parseJsonBody(w, r)
body, err := server.parseJsonBody(w, r)
if err != nil {
err.log.write(server.logger)
_ = err.write(w, r)
return
}
if body == nil {
err := newErrorResponse("expected JSON body in the request", 400, nil)
err.log.write(server.logger)
_ = err.write(w, r)
return
}
baseHandler(w, r, body)
}
return handler
}

func (server *Server) parseJsonBody(w http.ResponseWriter, r *http.Request) []byte {
func (server *Server) parseJsonBody(w http.ResponseWriter, r *http.Request) ([]byte, *ErrorResponse) {
if r.Body == nil {
response := newErrorResponse("expected JSON body in the request", 400, nil)
response.log.write(server.logger)
_ = response.write(w, r)
return nil
return nil, nil
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
msg := fmt.Sprintf("could not parse valid JSON from request: %s", err.Error())
response := newErrorResponse(msg, 400, nil)
response.log.write(server.logger)
_ = response.write(w, r)
return nil
err := newErrorResponse(msg, 400, nil)
return nil, err
}
return body
return body, nil
}

var regWhitespace *regexp.Regexp = regexp.MustCompile(`\s`)
Expand Down Expand Up @@ -286,6 +292,13 @@ func (server *Server) handleAuthMappingPOST(w http.ResponseWriter, r *http.Reque
ClientID string `json:"clientID"`
}{}

body, err := server.parseJsonBody(w, r)
if err != nil {
err.log.write(server.logger)
_ = err.write(w, r)
return
}

username := ""
clientID := ""
if authHeader := r.Header.Get("Authorization"); authHeader != "" {
Expand Down Expand Up @@ -320,10 +333,9 @@ func (server *Server) handleAuthMappingPOST(w http.ResponseWriter, r *http.Reque
_ = errResponse.write(w, r)
return
}
} else {
} else if len(body) > 0 {
// If they are not present in the token, fallback on the request body
server.logger.Info("No jwt provided, checking request body")
body := server.parseJsonBody(w, r)
err := json.Unmarshal(body, &requestBody)
if err != nil {
msg := fmt.Sprintf("could not parse JSON: %s", err.Error())
Expand All @@ -342,6 +354,17 @@ func (server *Server) handleAuthMappingPOST(w http.ResponseWriter, r *http.Reque
_ = errResponse.write(w, r)
return
}
} else {
// If no username or client ID provided in query string or JWT, return the
// auth mapping for the `anonymous` group. (See `docs/username.md` for more detail)
mappings, errResponse := authMappingForGroups(server.db, AnonymousGroup)
if errResponse != nil {
errResponse.log.write(server.logger)
_ = errResponse.write(w, r)
return
}
_ = jsonResponseFrom(mappings, http.StatusOK).write(w, r)
return
}

var mappings AuthMapping
Expand Down
47 changes: 24 additions & 23 deletions arborist/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2927,18 +2927,36 @@ func TestServer(t *testing.T) {
assert.Contains(t, result[resourcePath], action, msg)

// Expect response to also contain anonymous and loggedIn groups.
msg = fmt.Sprintf("Expected to see these auth mappings from anonymous group in response: %v", anonymousAuthMapping)
msg = fmt.Sprintf("Expected to see these auth mappings from anonymous group in response: %v, but got: %v", anonymousAuthMapping, result)
for resource, actions := range anonymousAuthMapping {
assert.Contains(t, result, resource, msg)
assert.ElementsMatch(t, result[resource], actions, msg)
}
msg = fmt.Sprintf("Expected to see these auth mappings from loggedIn group in response: %v", loggedInAuthMapping)
msg = fmt.Sprintf("Expected to see these auth mappings from loggedIn group in response: %v, but got: %v", loggedInAuthMapping, result)
for resource, actions := range loggedInAuthMapping {
assert.Contains(t, result, resource, msg)
assert.ElementsMatch(t, result[resource], actions, msg)
}
}

// testAnonymousAuthMappingResponse checks whether the AuthMapping in the HTTP response 'w'
// ONLY contains the correct resources and actions that belong to the anonymous group.
testAnonymousAuthMappingResponse := func(t *testing.T, w *httptest.ResponseRecorder) {
assert.Equal(t, w.Code, http.StatusOK, "expected a 200 OK")

// expect result to contain only authMappings of anonymous policies
result := make(arborist.AuthMapping)
err = json.Unmarshal(w.Body.Bytes(), &result)
if err != nil {
httpError(t, w, "couldn't read response from auth mapping")
}
msg := fmt.Sprintf("Expected these auth mappings from anonymous group: %v \t Got: %v", anonymousAuthMapping, result)
for resource, actions := range result {
assert.Contains(t, anonymousAuthMapping, resource, msg)
assert.ElementsMatch(t, anonymousAuthMapping[resource], actions, msg)
}
}

// testClientAuthMappingResponse checks whether the AuthMapping in the HTTP
// response 'w' contains the correct resources and actions that belong to the client.
// This does NOT include the resources and actions that belong to the anonymous and loggedIn groups.
Expand Down Expand Up @@ -2980,7 +2998,7 @@ func TestServer(t *testing.T) {
for k, v := range loggedInAuthMapping {
expectedMappings[k] = v
}
msg := fmt.Sprintf("Expected to see these auth mappings from anonymous and logged-in groups in response: %v", expectedMappings)
msg := fmt.Sprintf("Expected to see these auth mappings from anonymous and logged-in groups in response: %v, but got: %v", expectedMappings, result)
for resource, actions := range result {
assert.Contains(t, expectedMappings, resource, msg)
assert.ElementsMatch(t, expectedMappings[resource], actions, msg)
Expand Down Expand Up @@ -3035,24 +3053,7 @@ func TestServer(t *testing.T) {
url := "/auth/mapping"
req := newRequest("GET", url, nil)
handler.ServeHTTP(w, req)
if w.Code != http.StatusOK {
httpError(t, w, "expected to get policies for Anonymous group; got bad response instead")
}

// expect a 200 OK response
assert.Equal(t, w.Code, http.StatusOK, "expected a 200 OK")

// expect result to contain only authMappings of anonymous policies
result := make(arborist.AuthMapping)
err = json.Unmarshal(w.Body.Bytes(), &result)
if err != nil {
httpError(t, w, "couldn't read response from auth mapping")
}
msg := fmt.Sprintf("Expected these auth mappings from anonymous group: %v \t Got: %v", anonymousAuthMapping, result)
for resource, actions := range result {
assert.Contains(t, anonymousAuthMapping, resource, msg)
assert.ElementsMatch(t, anonymousAuthMapping[resource], actions, msg)
}
testAnonymousAuthMappingResponse(t, w)
})

t.Run("GET_expiredPolicy", func(t *testing.T) {
Expand Down Expand Up @@ -3119,7 +3120,7 @@ func TestServer(t *testing.T) {
body := []byte("")
req := newRequest("POST", "/auth/mapping", bytes.NewBuffer(body))
handler.ServeHTTP(w, req)
assert.Equal(t, w.Code, http.StatusBadRequest, "expected a 400 response")
testAnonymousAuthMappingResponse(t, w)
})

t.Run("bothUsernameAndClientIdProvided", func(t *testing.T) {
Expand Down Expand Up @@ -3243,7 +3244,7 @@ func TestServer(t *testing.T) {
w := httptest.NewRecorder()
req := newRequest("POST", "/auth/mapping", nil)
handler.ServeHTTP(w, req)
assert.Equal(t, w.Code, http.StatusBadRequest, "expected a 400 response")
testAnonymousAuthMappingResponse(t, w)
})
})

Expand Down
6 changes: 5 additions & 1 deletion docs/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ paths:
Note: Tokens which are linked to both a user and a client (token belonging to a client acting on behalf of a user) are supported, but the client's access is NOT taken into account. Only the user's access is returned.
Note that this endpoint does NOT support tokens generated through the OIDC "client_credentials" grant, which are linked to a client but not to a user. Calling this endpoint with such a token will not return the client's access.
Note: This endpoint does NOT support tokens generated through the OIDC "client_credentials" grant, which are linked to a client but not to a user. Calling this endpoint with such a token will not return the client's access.
If the specified user is not recognized by arborist, this endpoint returns
Expand Down Expand Up @@ -98,6 +98,10 @@ paths:
an empty response.
If no username or client ID is provided (no token is provided in the Authorization header, and there is no request body),
this endpoint returns ONLY the mappings available to members of the `anonymous` group.
Note: accepting a username or client ID in the body means that anyone can check anyone's authorization if they have their username. For this reason, this API is not meant to be exposed publicly.
requestBody:

Expand Down

0 comments on commit 936ef1b

Please sign in to comment.