Skip to content

Commit

Permalink
Merge b04a48d into dcdf900
Browse files Browse the repository at this point in the history
  • Loading branch information
rudyardrichter committed Jul 17, 2019
2 parents dcdf900 + b04a48d commit 27f6609
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 105 deletions.
43 changes: 43 additions & 0 deletions arborist/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"

"github.com/jmoiron/sqlx"
Expand Down Expand Up @@ -347,6 +348,48 @@ func evaluate(exp Expression, args []string, rows *sql.Rows) (bool, error) {
return rv, nil
}

func authRequestFromGET(decode func(string, []string) (*TokenInfo, error), r *http.Request) (*AuthRequest, *ErrorResponse) {
resourcePath := ""
resourcePathQS, ok := r.URL.Query()["resource"]
if ok {
resourcePath = resourcePathQS[0]
}
service := ""
serviceQS, ok := r.URL.Query()["service"]
if ok {
service = serviceQS[0]
}
method := ""
methodQS, ok := r.URL.Query()["method"]
if ok {
method = methodQS[0]
}
// get JWT from auth header and decode it
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
msg := "auth request missing auth header"
return nil, newErrorResponse(msg, 401, nil)
}
userJWT := strings.TrimPrefix(authHeader, "Bearer ")
userJWT = strings.TrimPrefix(userJWT, "bearer ")
aud := []string{"openid"}
info, err := decode(userJWT, aud)
if err != nil {
return nil, newErrorResponse(err.Error(), 401, &err)
}

authRequest := AuthRequest{
Username: info.username,
ClientID: info.clientID,
Policies: info.policies,
Resource: resourcePath,
Service: service,
Method: method,
}

return &authRequest, nil
}

func authorizedResources(db *sqlx.DB, request *AuthRequest) ([]ResourceFromQuery, *ErrorResponse) {
// if policies are specified in the request, we can use those (simplest query).
if request.Policies != nil && len(request.Policies) > 0 {
Expand Down
126 changes: 57 additions & 69 deletions arborist/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ func (server *Server) MakeRouter(out io.Writer) http.Handler {

router.Handle("/auth/proxy", http.HandlerFunc(server.handleAuthProxy)).Methods("GET")
router.Handle("/auth/request", http.HandlerFunc(server.parseJSON(server.handleAuthRequest))).Methods("POST")
router.Handle("/auth/resources", http.HandlerFunc(server.parseJSON(server.handleListAuthResources))).Methods("POST")
router.Handle("/auth/resources", http.HandlerFunc(server.handleListAuthResourcesGET)).Methods("GET")
router.Handle("/auth/resources", http.HandlerFunc(server.parseJSON(server.handleListAuthResourcesPOST))).Methods("POST")

router.Handle("/policy", http.HandlerFunc(server.handlePolicyList)).Methods("GET")
router.Handle("/policy", http.HandlerFunc(server.parseJSON(server.handlePolicyCreate))).Methods("POST")
Expand Down Expand Up @@ -206,67 +207,33 @@ func handleNotFound(w http.ResponseWriter, r *http.Request) {
}

func (server *Server) handleAuthProxy(w http.ResponseWriter, r *http.Request) {
// Get QS arguments
resourcePathQS, ok := r.URL.Query()["resource"]
if !ok {
msg := "auth proxy request missing `resource` argument"
server.logger.Info(msg)
errResponse := newErrorResponse(msg, 400, nil)
authRequest, errResponse := authRequestFromGET(server.decodeToken, r)
if errResponse != nil {
errResponse.log.write(server.logger)
_ = errResponse.write(w, r)
return
}
resourcePath := resourcePathQS[0]
serviceQS, ok := r.URL.Query()["service"]
if !ok {
msg := "auth proxy request missing `service` argument"
server.logger.Info(msg)
errResponse := newErrorResponse(msg, 400, nil)
_ = errResponse.write(w, r)
return
if authRequest.Resource == "" {
msg := "auth proxy request missing `resource` argument"
errResponse = newErrorResponse(msg, 400, nil)
}
service := serviceQS[0]
methodQS, ok := r.URL.Query()["method"]
if !ok {
msg := "auth proxy request missing `method` argument"
server.logger.Info(msg)
errResponse := newErrorResponse(msg, 400, nil)
_ = errResponse.write(w, r)
return
if authRequest.Service == "" {
msg := "auth proxy request missing `service` argument"
errResponse = newErrorResponse(msg, 400, nil)
}
method := methodQS[0]
// get JWT from auth header and decode it
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
msg := "auth proxy request missing auth header"
server.logger.Info(msg)
errResponse := newErrorResponse(msg, 401, nil)
_ = errResponse.write(w, r)
return
if authRequest.Method == "" {
msg := "auth request missing `method` argument"
errResponse = newErrorResponse(msg, 400, nil)
}
userJWT := strings.TrimPrefix(authHeader, "Bearer ")
userJWT = strings.TrimPrefix(userJWT, "bearer ")
aud := []string{"openid"}
info, err := server.decodeToken(userJWT, aud)
if err != nil {
server.logger.Info(err.Error())
errResponse := newErrorResponse(err.Error(), 401, &err)
if errResponse != nil {
errResponse.log.write(server.logger)
_ = errResponse.write(w, r)
return
}
authRequest.stmts = server.stmts
w.Header().Set("REMOTE_USER", authRequest.Username)

w.Header().Set("REMOTE_USER", info.username)

var authRequest = AuthRequest{
Username: info.username,
ClientID: info.clientID,
Policies: info.policies,
Resource: resourcePath,
Service: service,
Method: method,
stmts: server.stmts,
}

rv, err := authorizeUser(&authRequest)
rv, err := authorizeUser(authRequest)
if err != nil {
msg := fmt.Sprintf("could not authorize: %s", err.Error())
server.logger.Info("tried to handle auth request but input was invalid: %s", msg)
Expand All @@ -278,7 +245,7 @@ func (server *Server) handleAuthProxy(w http.ResponseWriter, r *http.Request) {
server.logger.Debug("user is authorized")
}
if err == nil && rv.Auth && authRequest.ClientID != "" {
rv, err = authorizeClient(&authRequest)
rv, err = authorizeClient(authRequest)
if err != nil {
msg := fmt.Sprintf("could not authorize: %s", err.Error())
server.logger.Info("tried to handle auth request but input was invalid: %s", msg)
Expand Down Expand Up @@ -419,11 +386,25 @@ func (server *Server) handleAuthRequest(w http.ResponseWriter, r *http.Request,
_ = jsonResponseFrom(result, 200).write(w, r)
}

func (server *Server) handleListAuthResources(w http.ResponseWriter, r *http.Request, body []byte) {
authRequest := struct {
func (server *Server) handleListAuthResourcesGET(w http.ResponseWriter, r *http.Request) {
authRequest := &AuthRequest{}
var errResponse *ErrorResponse
authRequest, errResponse = authRequestFromGET(server.decodeToken, r)
if errResponse != nil {
errResponse.log.write(server.logger)
_ = errResponse.write(w, r)
return
}
server.makeAuthResourcesResponse(w, r, authRequest, errResponse)
}

func (server *Server) handleListAuthResourcesPOST(w http.ResponseWriter, r *http.Request, body []byte) {
authRequest := &AuthRequest{}
var errResponse *ErrorResponse
request := struct {
User AuthRequestJSON_User `json:"user"`
}{}
err := json.Unmarshal(body, &authRequest)
err := json.Unmarshal(body, &request)
if err != nil {
msg := fmt.Sprintf("could not parse auth request from JSON: %s", err.Error())
server.logger.Info("tried to handle auth request but input was invalid: %s", msg)
Expand All @@ -434,44 +415,51 @@ func (server *Server) handleListAuthResources(w http.ResponseWriter, r *http.Req
// TODO
// make sure not empty
/*
if (authRequest.User == AuthRequestJSON_User{}) {
if (request.User == AuthRequestJSON_User{}) {
server.logger.Info("auth resources request missing user field", msg)
response := newErrorResponse(msg, 400, nil)
_ = response.write(w, r)
return
}
*/
var aud []string
if authRequest.User.Audiences == nil {
if request.User.Audiences == nil {
aud = []string{"openid"}
} else {
aud = make([]string, len(authRequest.User.Audiences))
copy(aud, authRequest.User.Audiences)
aud = make([]string, len(request.User.Audiences))
copy(aud, request.User.Audiences)
}

info, err := server.decodeToken(authRequest.User.Token, aud)
info, err := server.decodeToken(request.User.Token, aud)
if err != nil {
server.logger.Info(err.Error())
errResponse := newErrorResponse(err.Error(), 401, &err)
_ = errResponse.write(w, r)
return
}

request := &AuthRequest{
Username: info.username,
ClientID: info.clientID,
Policies: info.policies,
}
if authRequest.User.Policies != nil {
request.Policies = authRequest.User.Policies
authRequest.Username = info.username
authRequest.ClientID = info.clientID
authRequest.Policies = info.policies
if request.User.Policies != nil {
authRequest.Policies = request.User.Policies
}
server.makeAuthResourcesResponse(w, r, authRequest, errResponse)
}

resourcesFromQuery, errResponse := authorizedResources(server.db, request)
func (server *Server) makeAuthResourcesResponse(w http.ResponseWriter, r *http.Request, authRequest *AuthRequest, errResponse *ErrorResponse) {
if errResponse != nil {
errResponse.log.write(server.logger)
_ = errResponse.write(w, r)
return
}

resourcesFromQuery, errResponse := authorizedResources(server.db, authRequest)
if errResponse != nil {
errResponse.log.write(server.logger)
_ = errResponse.write(w, r)
return
}
resources := []ResourceOut{}
for _, resourceFromQuery := range resourcesFromQuery {
resources = append(resources, resourceFromQuery.standardize())
Expand Down
112 changes: 76 additions & 36 deletions arborist/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2803,44 +2803,84 @@ func TestServer(t *testing.T) {
createPolicyBytes(t, policyBody)
createUserBytes(t, userBody)
grantUserPolicy(t, username, policyName)

w := httptest.NewRecorder()
token := TestJWT{username: username}
body := []byte(fmt.Sprintf(`{"user": {"token": "%s"}}`, token.Encode()))
req := newRequest("POST", "/auth/resources", bytes.NewBuffer(body))
handler.ServeHTTP(w, req)
if w.Code != http.StatusOK {
httpError(t, w, "auth resources request failed")
}
// in this case, since the user has zero access yet, should be empty
result := struct {
Resources []string `json:"resources"`
}{}
err = json.Unmarshal(w.Body.Bytes(), &result)
if err != nil {
httpError(t, w, "couldn't read response from auth resources")
}
msg := fmt.Sprintf("got response body: %s", w.Body.String())
assert.Equal(t, []string{resourcePath}, result.Resources, msg)
// check the response returning tags is also correct
w = httptest.NewRecorder()
req = newRequest("POST", "/auth/resources?tags", bytes.NewBuffer(body))
handler.ServeHTTP(w, req)
if w.Code != http.StatusOK {
httpError(t, w, "auth resources request failed")
}
err = json.Unmarshal(w.Body.Bytes(), &result)
if err != nil {
httpError(t, w, "couldn't read response from auth resources")
}
msg = fmt.Sprintf("got response body: %s", w.Body.String())
assert.Equal(t, 1, len(result.Resources), msg)
if len(result.Resources) != 1 {
t.Fatal()
}
tag := result.Resources[0]
resource := getResourceWithPath(t, resourcePath)
assert.Equal(t, resource.Tag, tag, "mismatched tag from auth resources list")

t.Run("GET", func(t *testing.T) {
w := httptest.NewRecorder()
req := newRequest("GET", "/auth/resources", nil)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token.Encode()))
handler.ServeHTTP(w, req)
if w.Code != http.StatusOK {
httpError(t, w, "auth resources request failed")
}
result := struct {
Resources []string `json:"resources"`
}{}
err = json.Unmarshal(w.Body.Bytes(), &result)
if err != nil {
httpError(t, w, "couldn't read response from auth resources")
}
msg := fmt.Sprintf("got response body: %s", w.Body.String())
assert.Equal(t, []string{resourcePath}, result.Resources, msg)
// check the response returning tags is also correct
w = httptest.NewRecorder()
req = newRequest("GET", "/auth/resources?tags", nil)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token.Encode()))
handler.ServeHTTP(w, req)
if w.Code != http.StatusOK {
httpError(t, w, "auth resources request failed")
}
err = json.Unmarshal(w.Body.Bytes(), &result)
if err != nil {
httpError(t, w, "couldn't read response from auth resources")
}
msg = fmt.Sprintf("got response body: %s", w.Body.String())
assert.Equal(t, 1, len(result.Resources), msg)
if len(result.Resources) != 1 {
t.Fatal()
}
tag := result.Resources[0]
resource := getResourceWithPath(t, resourcePath)
assert.Equal(t, resource.Tag, tag, "mismatched tag from auth resources list")
})

t.Run("POST", func(t *testing.T) {
w := httptest.NewRecorder()
req := newRequest("POST", "/auth/resources", bytes.NewBuffer(body))
handler.ServeHTTP(w, req)
if w.Code != http.StatusOK {
httpError(t, w, "auth resources request failed")
}
result := struct {
Resources []string `json:"resources"`
}{}
err = json.Unmarshal(w.Body.Bytes(), &result)
if err != nil {
httpError(t, w, "couldn't read response from auth resources")
}
msg := fmt.Sprintf("got response body: %s", w.Body.String())
assert.Equal(t, []string{resourcePath}, result.Resources, msg)
// check the response returning tags is also correct
w = httptest.NewRecorder()
req = newRequest("POST", "/auth/resources?tags", bytes.NewBuffer(body))
handler.ServeHTTP(w, req)
if w.Code != http.StatusOK {
httpError(t, w, "auth resources request failed")
}
err = json.Unmarshal(w.Body.Bytes(), &result)
if err != nil {
httpError(t, w, "couldn't read response from auth resources")
}
msg = fmt.Sprintf("got response body: %s", w.Body.String())
assert.Equal(t, 1, len(result.Resources), msg)
if len(result.Resources) != 1 {
t.Fatal()
}
tag := result.Resources[0]
resource := getResourceWithPath(t, resourcePath)
assert.Equal(t, resource.Tag, tag, "mismatched tag from auth resources list")
})

t.Run("Policies", func(t *testing.T) {
w := httptest.NewRecorder()
Expand Down

0 comments on commit 27f6609

Please sign in to comment.