diff --git a/.gitignore b/.gitignore index 961c8762133..4893df54d36 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ node_modules/ *.iml *.exe .vagrant -*.log \ No newline at end of file +*.log +*.stackdump \ No newline at end of file diff --git a/cli/hydra-host/handler/core.go b/cli/hydra-host/handler/core.go index fa802e28a88..f228129326f 100644 --- a/cli/hydra-host/handler/core.go +++ b/cli/hydra-host/handler/core.go @@ -56,7 +56,7 @@ func (c *Core) Start(ctx *cli.Context) error { c.clientHandler = clients.NewHandler(c.Ctx.Osins, m) c.connectionHandler = connections.NewHandler(c.Ctx.Connections, m) c.providers = provider.NewRegistry(providers) - c.policyHandler = policies.NewHandler(c.Ctx.Policies, m, c.guard, j) + c.policyHandler = policies.NewHandler(c.Ctx.Policies, m, c.guard, j, c.Ctx.Osins) c.oauthHandler = &oauth.Handler{ Accounts: c.Ctx.Accounts, Policies: c.Ctx.Policies, diff --git a/client/http/client.go b/client/http/client.go index dd943bad505..ac71c0ab1f2 100644 --- a/client/http/client.go +++ b/client/http/client.go @@ -1,7 +1,6 @@ package http import ( - "bytes" "encoding/json" "fmt" "github.com/RangelReale/osin" @@ -14,7 +13,6 @@ import ( "golang.org/x/oauth2/clientcredentials" "net/http" "net/url" - "strconv" ) var isAllowed struct { @@ -64,33 +62,15 @@ func (c *HTTPClient) IsAuthenticated(token string) (bool, error) { } func isValidAuthenticationRequest(c *HTTPClient, token string, retry bool) (bool, error) { - if c.clientToken == nil { - if ct, err := c.clientConfig.Token(oauth2.NoContext); err != nil { - return false, errors.New(err) - } else if ct == nil { - return false, errors.New("Access token could not be retrieved") - } else { - c.clientToken = ct - } - return isValidAuthenticationRequest(c, token, false) - } - data := url.Values{} - client := &http.Client{} data.Set("token", token) - r, err := http.NewRequest("POST", pkg.JoinURL(c.ep, "/oauth2/introspect"), bytes.NewBufferString(data.Encode())) - if err != nil { - return false, err - } - - r.Header.Add("Authorization", c.clientToken.Type()+" "+c.clientToken.AccessToken) - r.Header.Add("Content-Type", "application/x-www-form-urlencoded") - r.Header.Add("Content-Length", strconv.Itoa(len(data.Encode()))) - resp, err := client.Do(r) - if err != nil { - return false, err + request := gorequest.New() + resp, body, errs := request.Post(pkg.JoinURL(c.ep, "/oauth2/introspect")).Type("form").SetBasicAuth(c.clientConfig.ClientID, c.clientConfig.ClientSecret).SendString(data.Encode()).End() + if len(errs) > 0 { + return false, errors.Errorf("Got errors: %v", errs) + } else if resp.StatusCode != http.StatusOK { + return false, errors.Errorf("Status code %d is not 200: %s", resp.StatusCode, body) } - defer resp.Body.Close() if retry && resp.StatusCode == http.StatusUnauthorized { var err error @@ -108,7 +88,7 @@ func isValidAuthenticationRequest(c *HTTPClient, token string, retry bool) (bool Active bool `json:"active"` } - if err := json.NewDecoder(resp.Body).Decode(&introspect); err != nil { + if err := json.Unmarshal([]byte(body), &introspect); err != nil { return false, err } else if !introspect.Active { return false, errors.New("Authentication denied") @@ -117,19 +97,8 @@ func isValidAuthenticationRequest(c *HTTPClient, token string, retry bool) (bool } func isValidAuthorizeRequest(c *HTTPClient, ar *AuthorizeRequest, retry bool) (bool, error) { - if c.clientToken == nil { - if ct, err := c.clientConfig.Token(oauth2.NoContext); err != nil { - return false, errors.New(err) - } else if ct == nil { - return false, errors.New("Access token could not be retrieved") - } else { - c.clientToken = ct - } - return isValidAuthorizeRequest(c, ar, false) - } - request := gorequest.New() - resp, body, errs := request.Post(pkg.JoinURL(c.ep, "/guard/allowed")).Set("Authorization", c.clientToken.Type()+" "+c.clientToken.AccessToken).Set("Content-Type", "application/json").Send(*ar).End() + resp, body, errs := request.Post(pkg.JoinURL(c.ep, "/guard/allowed")).SetBasicAuth(c.clientConfig.ClientID, c.clientConfig.ClientSecret).Set("Content-Type", "application/json").Send(*ar).End() if len(errs) > 0 { return false, errors.Errorf("Got errors: %v", errs) } else if retry && resp.StatusCode == http.StatusUnauthorized { diff --git a/client/http/client_test.go b/client/http/client_test.go index 9852d7a656a..1658343ae8b 100644 --- a/client/http/client_test.go +++ b/client/http/client_test.go @@ -2,7 +2,6 @@ package http_test import ( "encoding/json" - "fmt" "github.com/go-errors/errors" "github.com/gorilla/mux" "github.com/ory-am/common/pkg" @@ -33,8 +32,8 @@ func tokenHandler(rw http.ResponseWriter, req *http.Request) { func TestIsRequestAllowed(t *testing.T) { router := mux.NewRouter() router.HandleFunc("/guard/allowed", func(rw http.ResponseWriter, req *http.Request) { - if req.Header.Get("Authorization") != "Bearer fetch-token-ok" { - http.Error(rw, "", http.StatusUnauthorized) + if req.Header.Get("Authorization") != "Basic YXBwOmtleQ==" { + http.Error(rw, req.Header.Get("Authorization"), http.StatusUnauthorized) return } pkg.WriteJSON(rw, struct { @@ -45,125 +44,19 @@ func TestIsRequestAllowed(t *testing.T) { ts := httptest.NewServer(router) defer ts.Close() - c := New(ts.URL, "irrelevant", "irrelephant") + c := New(ts.URL, "app", "key") c.SetClientToken(&oauth2.Token{TokenType: "bearer", AccessToken: "foobar"}) allowed, err := c.IsRequestAllowed(&http.Request{Header: http.Header{"Authorization": []string{"Bearer token"}}}, "", "", "") assert.Nil(t, err) assert.True(t, allowed) } -func TestIsAllowedRetriesOnlyOnceWhenTokenIsInvalid(t *testing.T) { - var count int - router := mux.NewRouter() - router.HandleFunc("/oauth2/token", tokenHandler).Methods("POST") - router.HandleFunc("/guard/allowed", func(rw http.ResponseWriter, req *http.Request) { - http.Error(rw, fmt.Sprintf("token invalid try ", count), http.StatusUnauthorized) - count++ - }).Methods("POST") - ts := httptest.NewServer(router) - defer ts.Close() - c := New(ts.URL, "irrelevant", "irrelephant") - c.SetClientToken(&oauth2.Token{TokenType: "bearer", AccessToken: "foobar"}) - - allowed, err := c.IsAllowed(&AuthorizeRequest{ - Permission: "foo", - Token: "bar", - Resource: "res", - Context: &operator.Context{ - Owner: "foo", - }, - }) - assert.NotNil(t, err) - assert.False(t, allowed) - assert.Equal(t, 2, count) -} - -func TestIsAllowedRetriesWhenTokenIsExpired(t *testing.T) { - var try int - router := mux.NewRouter() - router.HandleFunc("/guard/allowed", func(rw http.ResponseWriter, req *http.Request) { - try++ - if try == 1 { - t.Logf("token invalid try ", try) - http.Error(rw, fmt.Sprintf("token invalid try ", try), http.StatusUnauthorized) - return - } - - pkg.WriteJSON(rw, struct { - Allowed bool `json:"allowed"` - }{Allowed: true}) - }).Methods("POST") - router.HandleFunc("/oauth2/token", tokenHandler).Methods("POST") - ts := httptest.NewServer(router) - defer ts.Close() - - c := New(ts.URL, "irrelevant", "irrelephant") - c.SetClientToken(&oauth2.Token{TokenType: "bearer", AccessToken: "foobar"}) - - allowed, err := c.IsAllowed(&AuthorizeRequest{ - Permission: "foo", - Token: "bar", - Resource: "res", - Context: &operator.Context{ - Owner: "foo", - }, - }) - assert.Nil(t, err, "%s", err) - assert.True(t, allowed) - assert.Equal(t, 2, try) -} - -func TestIsAuthenticatedRetriesWhenTokenIsExpired(t *testing.T) { - var try int - router := mux.NewRouter() - router.HandleFunc("/oauth2/introspect", func(rw http.ResponseWriter, req *http.Request) { - try++ - if try == 1 { - http.Error(rw, fmt.Sprintf("token invalid try ", try), http.StatusUnauthorized) - return - } - - pkg.WriteJSON(rw, struct { - Active bool `json:"active"` - }{Active: true}) - }).Methods("POST") - router.HandleFunc("/oauth2/token", tokenHandler).Methods("POST") - ts := httptest.NewServer(router) - defer ts.Close() - - c := New(ts.URL, "irrelevant", "irrelephant") - c.SetClientToken(&oauth2.Token{TokenType: "bearer", AccessToken: "client"}) - active, err := c.IsAuthenticated("federated.token") - assert.Nil(t, err, "%s", err) - assert.True(t, active) - assert.Equal(t, 2, try) -} - -func TestIsAuthenticatedRetriesOnlyOnceWhenTokenIsExpired(t *testing.T) { - var count int - router := mux.NewRouter() - router.HandleFunc("/oauth2/introspect", func(rw http.ResponseWriter, req *http.Request) { - count++ - http.Error(rw, fmt.Sprintf("token invalid try ", count), http.StatusUnauthorized) - }).Methods("POST") - router.HandleFunc("/oauth2/token", tokenHandler).Methods("POST") - ts := httptest.NewServer(router) - defer ts.Close() - - c := New(ts.URL, "irrelevant", "irrelephant") - c.SetClientToken(&oauth2.Token{TokenType: "bearer", AccessToken: "client"}) - active, err := c.IsAuthenticated("federated.token") - assert.NotNil(t, err) - assert.False(t, active) - assert.Equal(t, 2, count) -} - func TestIsAllowed(t *testing.T) { router := mux.NewRouter() router.HandleFunc("/oauth2/token", tokenHandler).Methods("POST") router.HandleFunc("/guard/allowed", func(rw http.ResponseWriter, req *http.Request) { - if req.Header.Get("Authorization") != "Bearer fetch-token-ok" { - http.Error(rw, "", http.StatusUnauthorized) + if req.Header.Get("Authorization") != "Basic YXBwOmtleQ==" { + http.Error(rw, req.Header.Get("Authorization"), http.StatusUnauthorized) return } var p handler.GrantedPayload @@ -185,9 +78,9 @@ func TestIsAllowed(t *testing.T) { ts := httptest.NewServer(router) defer ts.Close() - c := New(ts.URL, "irrelevant", "irrelephant") + c := New(ts.URL, "app", "key") allowed, err := c.IsAllowed(&AuthorizeRequest{Permission: "foo", Token: "bar", Resource: "res", Context: &operator.Context{Owner: "foo"}}) - assert.Nil(t, err) + assert.Nil(t, err, "%s", err) assert.True(t, allowed) } @@ -196,8 +89,8 @@ func TestIsAuthenticated(t *testing.T) { called := false router.HandleFunc("/oauth2/token", tokenHandler).Methods("POST") router.HandleFunc("/oauth2/introspect", func(rw http.ResponseWriter, req *http.Request) { - if req.Header.Get("Authorization") != "Bearer fetch-token-ok" { - http.Error(rw, "", http.StatusUnauthorized) + if req.Header.Get("Authorization") != "Basic YXBwOmtleQ==" { + http.Error(rw, req.Header.Get("Authorization"), http.StatusUnauthorized) return } req.ParseForm() @@ -210,9 +103,9 @@ func TestIsAuthenticated(t *testing.T) { ts := httptest.NewServer(router) defer ts.Close() - c := New(ts.URL, "irrelevant", "irrelephant") - active, err := c.IsAuthenticated("federated.token") - assert.Nil(t, err) + c := New(ts.URL, "app", "key") + active, err := c.IsAuthenticated("some.token") + assert.Nil(t, err, "%s", err) assert.True(t, active) assert.True(t, called) } diff --git a/oauth/handler/handler.go b/oauth/handler/handler.go index 0cd6201defc..83c123f3210 100644 --- a/oauth/handler/handler.go +++ b/oauth/handler/handler.go @@ -2,10 +2,10 @@ package handler import ( "encoding/json" - "errors" "fmt" "github.com/RangelReale/osin" log "github.com/Sirupsen/logrus" + "github.com/go-errors/errors" "github.com/gorilla/mux" hctx "github.com/ory-am/common/handler" "github.com/ory-am/common/pkg" @@ -77,7 +77,7 @@ func (h *Handler) SetRoutes(r *mux.Router, extractor func(h hctx.ContextHandler) func (h *Handler) RevokeHandler(w http.ResponseWriter, r *http.Request) { auth, err := osin.CheckBasicAuth(r) if err != nil { - pkg.HttpError(w, errors.New("Unauthorized"), http.StatusServiceUnavailable) + pkg.HttpError(w, errors.New("Unauthorized"), http.StatusUnauthorized) return } else if auth == nil { pkg.HttpError(w, errors.New("Unauthorized"), http.StatusUnauthorized) @@ -86,7 +86,10 @@ func (h *Handler) RevokeHandler(w http.ResponseWriter, r *http.Request) { client, err := h.OAuthStore.GetClient(auth.Username) if err != nil { - pkg.HttpError(w, errors.New("Unauthorized"), http.StatusServiceUnavailable) + pkg.HttpError(w, errors.New("Unauthorized"), http.StatusUnauthorized) + return + } else if client.GetSecret() != auth.Password { + pkg.HttpError(w, errors.New("Unauthorized"), http.StatusUnauthorized) return } @@ -161,6 +164,15 @@ func (h *Handler) IntrospectHandler(w http.ResponseWriter, r *http.Request) { return } + client, err := h.OAuthStore.GetClient(auth.Username) + if err != nil { + pkg.HttpError(w, errors.New("Unauthorized"), http.StatusUnauthorized) + return + } else if client.GetSecret() != auth.Password { + pkg.HttpError(w, errors.New("Unauthorized"), http.StatusUnauthorized) + return + } + result := make(map[string]interface{}) result["active"] = false diff --git a/oauth/handler/handler_test.go b/oauth/handler/handler_test.go index a00e9c63b66..262ff621f68 100644 --- a/oauth/handler/handler_test.go +++ b/oauth/handler/handler_test.go @@ -296,38 +296,92 @@ func TestIntrospect(t *testing.T) { require.Nil(t, err) for k, c := range []*struct { - accessToken string - code int - pass bool + accessToken string + code int + pass bool + clientID string + clientSecret string }{ - {verify.AccessToken, http.StatusOK, true}, - {access.AccessToken, http.StatusOK, true}, - {"", http.StatusOK, false}, - {" ", http.StatusOK, false}, - {"invalid", http.StatusOK, false}, + { + accessToken: verify.AccessToken, + code: http.StatusUnauthorized, + pass: false, + clientSecret: "not-working", + }, + { + accessToken: verify.AccessToken, + code: http.StatusUnauthorized, + pass: false, + clientID: "not-existing", + }, + { + accessToken: verify.AccessToken, + code: http.StatusOK, + pass: true, + }, + { + accessToken: access.AccessToken, + code: http.StatusOK, + pass: true, + }, + { + accessToken: "", + code: http.StatusOK, + pass: false, + }, + { + accessToken: " ", + code: http.StatusOK, + pass: false, + }, + { + accessToken: "invalid", + code: http.StatusOK, + pass: false, + }, // - {"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.e30.FvuwHdEjgGxPAyVUb-eqtiPl2gycU9WOHNzwpFKcpdN_QkXkBUxU3qFl3lLBaMzIuP_GjXLXcJZFhyQ2Ne3kfWuZSGLmob0Og8B4lAy7CA7iwpji2R3aUcwBwbJ41IJa__F8fMRz0dRDwhyrBKD-9y4TfV_-yZuzBZxq0UdjX6IdpzsdetphBSIZkPij5MY3thRwC-X_gXyIXi4-G2_CjRrV5lCGnPJrDbLqPCYqS71wK9NEsz_B8p5ENmwad8vZe4fEFR7XsqJrhPjbEVGeLpzSz0AOGp4G1iyvv1sdu4M3Y8KSSGYnZ8lXNGyi8QeUr374Y6XgJ5N5TVLWI2cMxg", http.StatusOK, false}, + { + accessToken: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.e30.FvuwHdEjgGxPAyVUb-eqtiPl2gycU9WOHNzwpFKcpdN_QkXkBUxU3qFl3lLBaMzIuP_GjXLXcJZFhyQ2Ne3kfWuZSGLmob0Og8B4lAy7CA7iwpji2R3aUcwBwbJ41IJa__F8fMRz0dRDwhyrBKD-9y4TfV_-yZuzBZxq0UdjX6IdpzsdetphBSIZkPij5MY3thRwC-X_gXyIXi4-G2_CjRrV5lCGnPJrDbLqPCYqS71wK9NEsz_B8p5ENmwad8vZe4fEFR7XsqJrhPjbEVGeLpzSz0AOGp4G1iyvv1sdu4M3Y8KSSGYnZ8lXNGyi8QeUr374Y6XgJ5N5TVLWI2cMxg", + code: http.StatusOK, + pass: false, + }, // "exp": 12345 - {"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtYXgiLCJleHAiOjEyMzQ1fQ.0w2dienBCvgfbhLjmK04fFKqf2oFRMNoKS0A3zHBpU_yN22utC_gAvcFwKiMffebtHah7rgldnPqNZaNhfnEM1PxNFh46vXO5LNZDHt5sNZqeBtZ1Q7ORkZsAtIp97mtZMxufn0VBqJTRYxyDrEzH9Mo1OpXuPTzDP87n-p_Xdbpj5YccZU6TZ11eLs9NvuYu_A2HClKrGbCeaHFAGVWVaoSZ_TvjGqyBI-XoGzuCEBoj6NFTHxZpbNeKhVTTwXHv2sUn09gZ_ErmbPZKExV5sCLETktr4ABUXkNtw4xLW6g0EVzC9dRMKxUZO8kCmAJkKHUTinEDjpfX_n8CKRQVQ", http.StatusOK, false}, + { + accessToken: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtYXgiLCJleHAiOjEyMzQ1fQ.0w2dienBCvgfbhLjmK04fFKqf2oFRMNoKS0A3zHBpU_yN22utC_gAvcFwKiMffebtHah7rgldnPqNZaNhfnEM1PxNFh46vXO5LNZDHt5sNZqeBtZ1Q7ORkZsAtIp97mtZMxufn0VBqJTRYxyDrEzH9Mo1OpXuPTzDP87n-p_Xdbpj5YccZU6TZ11eLs9NvuYu_A2HClKrGbCeaHFAGVWVaoSZ_TvjGqyBI-XoGzuCEBoj6NFTHxZpbNeKhVTTwXHv2sUn09gZ_ErmbPZKExV5sCLETktr4ABUXkNtw4xLW6g0EVzC9dRMKxUZO8kCmAJkKHUTinEDjpfX_n8CKRQVQ", + code: http.StatusOK, + pass: false, + }, // { // "exp": 1924975619, // "nbf": 1924975619 // } - {"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtYXgiLCJleHAiOjE5MjQ5NzU2MTksIm5iZiI6MTkyNDk3NTYxOX0.P381fgXq75I1iFBFMA624LgKm-wyous9VV4aQHS2O9kDyCJUejK71-M5owaWkjDOkHFlE7Ju5yknasODNlYsuzB2ujos1xiCuHYjoqivvSPNwrxJMXKMXrtzzk045E_OH1EHd_d9KVmrnA5dd3NLqNdYAoUogrO4TistjpZOv-ABUesiKIOR6SopD2tUxHog4RmFFtBJOt4l9P2aGn4a6LBt5wvBz9wUKak7YzUKMZXsWus-x-RP41bulpsUPEfH4TtgQHOM-VQ5W-EORhH8PClBfUrPyp1H7bgXOjhvCdpf4dfJS59Wf3euq9TXT0axyJ5HErXy3yOwC0E2ggl2iQ", http.StatusOK, false}, + { + accessToken: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtYXgiLCJleHAiOjE5MjQ5NzU2MTksIm5iZiI6MTkyNDk3NTYxOX0.P381fgXq75I1iFBFMA624LgKm-wyous9VV4aQHS2O9kDyCJUejK71-M5owaWkjDOkHFlE7Ju5yknasODNlYsuzB2ujos1xiCuHYjoqivvSPNwrxJMXKMXrtzzk045E_OH1EHd_d9KVmrnA5dd3NLqNdYAoUogrO4TistjpZOv-ABUesiKIOR6SopD2tUxHog4RmFFtBJOt4l9P2aGn4a6LBt5wvBz9wUKak7YzUKMZXsWus-x-RP41bulpsUPEfH4TtgQHOM-VQ5W-EORhH8PClBfUrPyp1H7bgXOjhvCdpf4dfJS59Wf3euq9TXT0axyJ5HErXy3yOwC0E2ggl2iQ", + code: http.StatusOK, + pass: false, + }, // { // "exp": 1924975619, // "iat": 1924975619, // "nbf": 0 // } - {"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtYXgiLCJleHAiOjE5MjQ5NzU2MTksIm5iZiI6MCwiaWF0IjoxOTI0OTc1NjE5fQ.qwUo8-e9tcg69pv9SJFpMXytJtAZlTJoVZh73bVtpkImZ0G5s_cbzPvccM_LmmHl5rFCpQuwWDSuHME2iyer6-gC2DILGQiXyJ5JhJdAKD4xtSFnV90zu84BF8L4JWqLeIEV13AHTpphfS0tOOOKL6sFYbo4LQVslfRYON28D3iOP-YAKJeorHsZgTNg-7VjPC8w_emDpVoNiWEyON2gHrucKiJlWQJVE_gxLf_n-F29UV1OBi-AjxccCrXMd0pzndZ7zg_7EbaUuOmLStfn2ORkoARaHaw55Sv2vbf_AV0MWsgqPaOlK6GTbfv3sYjB7K9eItWh9o8kDXNM4blqSw", http.StatusOK, false}, + { + accessToken: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtYXgiLCJleHAiOjE5MjQ5NzU2MTksIm5iZiI6MCwiaWF0IjoxOTI0OTc1NjE5fQ.qwUo8-e9tcg69pv9SJFpMXytJtAZlTJoVZh73bVtpkImZ0G5s_cbzPvccM_LmmHl5rFCpQuwWDSuHME2iyer6-gC2DILGQiXyJ5JhJdAKD4xtSFnV90zu84BF8L4JWqLeIEV13AHTpphfS0tOOOKL6sFYbo4LQVslfRYON28D3iOP-YAKJeorHsZgTNg-7VjPC8w_emDpVoNiWEyON2gHrucKiJlWQJVE_gxLf_n-F29UV1OBi-AjxccCrXMd0pzndZ7zg_7EbaUuOmLStfn2ORkoARaHaw55Sv2vbf_AV0MWsgqPaOlK6GTbfv3sYjB7K9eItWh9o8kDXNM4blqSw", + code: http.StatusOK, + pass: false, + }, // { // "exp": 1924975619, // "iat": 1924975619, // "nbf": 0 // "aud": "wrong-audience" // } - {"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtYXgiLCJleHAiOjE5MjQ5NzU2MTksIm5iZiI6MCwiaWF0IjoxOTI0OTc1NjE5LCJhdWQiOiJ3cm9uZy1hdWRpZW5jZSJ9.ZDyeQYDEjUUUvrzD_7t-4OHc4KOv4r46soSNMURZCpktCBP0qEeVovjLRHILmMlTxb1ItiOoUs2y7O-WYOKz182evgs1dkfX3C8LrOlDD3IoimaHNK4jW-5pYM47NFnW52Y7jp802wOQ8_UwERr5iu0Mb5trQC3RPALE17ppkplQVbL54kxu4HaQsPd4A2Qe2uIPhr-x75BPQiiaqzdRWuDwJhmpYBwLvyxKIY4B-AHBk70H7lpitDRXNMJdunIrIhz-qpkO7_XiwaBzwHHmdl9uRMU-UNC0TyA0iM84R_y8YJsz8Xl3MXU7QVNARzo2GGbnm4T2aRv8E98aeBsNQw", http.StatusOK, false}, + { + accessToken: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtYXgiLCJleHAiOjE5MjQ5NzU2MTksIm5iZiI6MCwiaWF0IjoxOTI0OTc1NjE5LCJhdWQiOiJ3cm9uZy1hdWRpZW5jZSJ9.ZDyeQYDEjUUUvrzD_7t-4OHc4KOv4r46soSNMURZCpktCBP0qEeVovjLRHILmMlTxb1ItiOoUs2y7O-WYOKz182evgs1dkfX3C8LrOlDD3IoimaHNK4jW-5pYM47NFnW52Y7jp802wOQ8_UwERr5iu0Mb5trQC3RPALE17ppkplQVbL54kxu4HaQsPd4A2Qe2uIPhr-x75BPQiiaqzdRWuDwJhmpYBwLvyxKIY4B-AHBk70H7lpitDRXNMJdunIrIhz-qpkO7_XiwaBzwHHmdl9uRMU-UNC0TyA0iM84R_y8YJsz8Xl3MXU7QVNARzo2GGbnm4T2aRv8E98aeBsNQw", + code: http.StatusOK, + pass: false, + }, // { // "exp": 1924975619, // "iat": 1924975619, @@ -335,7 +389,11 @@ func TestIntrospect(t *testing.T) { // "sub": "max", // "aud": "wrong-audience" // } - {"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE5MjQ5NzU2MTksIm5iZiI6MCwiaWF0IjoxOTI0OTc1NjE5LCJhdWQiOiJ3cm9uZy1hdWRpZW5jZSIsInN1YiI6Im1heCJ9.OBKaAS6l7Ie-y5T6-r5Kk0MyLxxeoYJZ5MizZazAc1gon1J5yi0pCcwhP0a-cKUuJbuvgyw9PF1iutykRYy9cSd9ducEpL9PLhUAwIOOyQxp35udGPOOaf0hQAOBUzP--I6SqaIOZXAfWg6_HefRcYhqy8m-iagWLXZ7RT4sMrEVzHUq6fWM6f2HDid0CxCjH6OL5ScZebqUNVimCqZkaQ7Fn9TAnlcKnlDDOmZhfZEAOMNqlUvC7mLBbbhuiX0eUtdnchhXLjuLn67PcxYi7KpEFDKwGhN2eN0t73RWIpMz-YlU77HNTEvm-AzdG-BoqBgSrGnPUlU6Mdfhz7IeMA", http.StatusOK, false}, + { + accessToken: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE5MjQ5NzU2MTksIm5iZiI6MCwiaWF0IjoxOTI0OTc1NjE5LCJhdWQiOiJ3cm9uZy1hdWRpZW5jZSIsInN1YiI6Im1heCJ9.OBKaAS6l7Ie-y5T6-r5Kk0MyLxxeoYJZ5MizZazAc1gon1J5yi0pCcwhP0a-cKUuJbuvgyw9PF1iutykRYy9cSd9ducEpL9PLhUAwIOOyQxp35udGPOOaf0hQAOBUzP--I6SqaIOZXAfWg6_HefRcYhqy8m-iagWLXZ7RT4sMrEVzHUq6fWM6f2HDid0CxCjH6OL5ScZebqUNVimCqZkaQ7Fn9TAnlcKnlDDOmZhfZEAOMNqlUvC7mLBbbhuiX0eUtdnchhXLjuLn67PcxYi7KpEFDKwGhN2eN0t73RWIpMz-YlU77HNTEvm-AzdG-BoqBgSrGnPUlU6Mdfhz7IeMA", + code: http.StatusOK, + pass: false, + }, // { // "exp": 1924975619, // "iat": 1924975619, @@ -343,10 +401,48 @@ func TestIntrospect(t *testing.T) { // "aud": "tests", // "subject": "foo" // } - {"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtYXgiLCJleHAiOjE5MjQ5NzU2MTksIm5iZiI6MCwiaWF0IjoxOTI0OTc1NjE5LCJhdWQiOiJ0ZXN0cyIsInN1YmplY3QiOiJmb28ifQ.lvjLGnLO3mZSS63fomK-KH2mhLXjjg9b13opiN7jY4MrXE_DaR0Lum8a_RcqqSTXbpHxYSIPV9Ji7zM_X1bvBtsPpBE1PR3_PrdD5_uIDQ-UWPVzozxhOvuZzU7qHx3TFQClZ6tYIXYioTszz9zQHiE4hj1x6Z_shWPfczELGyD0HnEC3o_w7IFfYO_L0YDN_vkuqr6yS5kaPIsoCF_iHuhTzoBAEIpUENlxSpCPuxR9aMaJ-BQDInHoPc1h-VvkgOdR_iENQdOUePObw17ywdGkRk6C5kRHSxjca-ULGcDn36NZ54SEPolcGbjs3vVA1g0jQARKIcTVw6Uu7x0s6Q", http.StatusOK, true}, + { + accessToken: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtYXgiLCJleHAiOjE5MjQ5NzU2MTksIm5iZiI6MCwiaWF0IjoxOTI0OTc1NjE5LCJhdWQiOiJ0ZXN0cyIsInN1YmplY3QiOiJmb28ifQ.lvjLGnLO3mZSS63fomK-KH2mhLXjjg9b13opiN7jY4MrXE_DaR0Lum8a_RcqqSTXbpHxYSIPV9Ji7zM_X1bvBtsPpBE1PR3_PrdD5_uIDQ-UWPVzozxhOvuZzU7qHx3TFQClZ6tYIXYioTszz9zQHiE4hj1x6Z_shWPfczELGyD0HnEC3o_w7IFfYO_L0YDN_vkuqr6yS5kaPIsoCF_iHuhTzoBAEIpUENlxSpCPuxR9aMaJ-BQDInHoPc1h-VvkgOdR_iENQdOUePObw17ywdGkRk6C5kRHSxjca-ULGcDn36NZ54SEPolcGbjs3vVA1g0jQARKIcTVw6Uu7x0s6Q", + code: http.StatusUnauthorized, + clientSecret: uuid.New(), + pass: false, + }, + // { + // "exp": 1924975619, + // "iat": 1924975619, + // "nbf": 0 + // "aud": "tests", + // "subject": "foo" + // } + { + accessToken: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtYXgiLCJleHAiOjE5MjQ5NzU2MTksIm5iZiI6MCwiaWF0IjoxOTI0OTc1NjE5LCJhdWQiOiJ0ZXN0cyIsInN1YmplY3QiOiJmb28ifQ.lvjLGnLO3mZSS63fomK-KH2mhLXjjg9b13opiN7jY4MrXE_DaR0Lum8a_RcqqSTXbpHxYSIPV9Ji7zM_X1bvBtsPpBE1PR3_PrdD5_uIDQ-UWPVzozxhOvuZzU7qHx3TFQClZ6tYIXYioTszz9zQHiE4hj1x6Z_shWPfczELGyD0HnEC3o_w7IFfYO_L0YDN_vkuqr6yS5kaPIsoCF_iHuhTzoBAEIpUENlxSpCPuxR9aMaJ-BQDInHoPc1h-VvkgOdR_iENQdOUePObw17ywdGkRk6C5kRHSxjca-ULGcDn36NZ54SEPolcGbjs3vVA1g0jQARKIcTVw6Uu7x0s6Q", + code: http.StatusUnauthorized, + clientID: uuid.New(), + clientSecret: uuid.New(), + pass: false, + }, + // { + // "exp": 1924975619, + // "iat": 1924975619, + // "nbf": 0 + // "aud": "tests", + // "subject": "foo" + // } + { + accessToken: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtYXgiLCJleHAiOjE5MjQ5NzU2MTksIm5iZiI6MCwiaWF0IjoxOTI0OTc1NjE5LCJhdWQiOiJ0ZXN0cyIsInN1YmplY3QiOiJmb28ifQ.lvjLGnLO3mZSS63fomK-KH2mhLXjjg9b13opiN7jY4MrXE_DaR0Lum8a_RcqqSTXbpHxYSIPV9Ji7zM_X1bvBtsPpBE1PR3_PrdD5_uIDQ-UWPVzozxhOvuZzU7qHx3TFQClZ6tYIXYioTszz9zQHiE4hj1x6Z_shWPfczELGyD0HnEC3o_w7IFfYO_L0YDN_vkuqr6yS5kaPIsoCF_iHuhTzoBAEIpUENlxSpCPuxR9aMaJ-BQDInHoPc1h-VvkgOdR_iENQdOUePObw17ywdGkRk6C5kRHSxjca-ULGcDn36NZ54SEPolcGbjs3vVA1g0jQARKIcTVw6Uu7x0s6Q", + code: http.StatusOK, + pass: true, + }, } { data := url.Values{"token": []string{c.accessToken}} - resp, body, errs := gorequest.New().Post(ts.URL+"/oauth2/introspect").Type("form").SetBasicAuth(config.ClientID, config.ClientSecret).SendString(data.Encode()).End() + if c.clientID == "" { + c.clientID = configs["working"].ClientID + } + if c.clientSecret == "" { + c.clientSecret = configs["working"].ClientSecret + } + + resp, body, errs := gorequest.New().Post(ts.URL+"/oauth2/introspect").Type("form").SetBasicAuth(c.clientID, c.clientSecret).SendString(data.Encode()).End() require.Len(t, errs, 0) require.Equal(t, c.code, resp.StatusCode, "Case %d: %s", k, body) if resp.StatusCode != http.StatusOK { @@ -426,7 +522,7 @@ func TestRevoke(t *testing.T) { token: tokens[2].AccessToken, clientID: " ", clientSecret: " ", - expectStatusCode: http.StatusServiceUnavailable, + expectStatusCode: http.StatusUnauthorized, }, { token: tokens[3].RefreshToken, @@ -434,11 +530,17 @@ func TestRevoke(t *testing.T) { clientSecret: configs["working-2"].ClientSecret, expectStatusCode: http.StatusOK, }, + { + token: tokens[3].RefreshToken, + clientID: configs["working-2"].ClientID, + clientSecret: "not working", + expectStatusCode: http.StatusUnauthorized, + }, { token: tokens[0].RefreshToken, clientID: "foo", - clientSecret: "barbazfoobar", - expectStatusCode: http.StatusServiceUnavailable, + clientSecret: "wrong secret", + expectStatusCode: http.StatusUnauthorized, }, } { if c.clientID == "" { diff --git a/policy/handler/handler.go b/policy/handler/handler.go index 15f7f76cf44..269fee906fe 100644 --- a/policy/handler/handler.go +++ b/policy/handler/handler.go @@ -16,6 +16,7 @@ import ( "golang.org/x/net/context" "net/http" + "github.com/RangelReale/osin" log "github.com/Sirupsen/logrus" "github.com/go-errors/errors" "github.com/ory-am/hydra/jwt" @@ -27,6 +28,7 @@ import ( type Handler struct { s Storage m middleware.Middleware + o osin.Storage g Guarder j *jwt.JWT } @@ -51,15 +53,13 @@ func permission(id string) string { return fmt.Sprintf("rn:hydra:policies:%s", id) } -func NewHandler(s Storage, m middleware.Middleware, g Guarder, j *jwt.JWT) *Handler { - return &Handler{s: s, m: m, g: g, j: j} +func NewHandler(s Storage, m middleware.Middleware, g Guarder, j *jwt.JWT, o osin.Storage) *Handler { + return &Handler{s: s, m: m, g: g, j: j, o: o} } func (h *Handler) SetRoutes(r *mux.Router, extractor func(h hctx.ContextHandler) hctx.ContextHandler) { r.Handle("/guard/allowed", hctx.NewContextAdapter( context.Background(), - extractor, - h.m.IsAuthenticated, ).ThenFunc(h.Granted)).Methods("POST") r.Handle("/policies", hctx.NewContextAdapter( @@ -83,6 +83,24 @@ func (h *Handler) SetRoutes(r *mux.Router, extractor func(h hctx.ContextHandler) } func (h *Handler) Granted(ctx context.Context, rw http.ResponseWriter, req *http.Request) { + auth, err := osin.CheckBasicAuth(req) + if err != nil { + pkg.HttpError(rw, errors.New("Unauthorized"), http.StatusUnauthorized) + return + } else if auth == nil { + pkg.HttpError(rw, errors.New("Unauthorized"), http.StatusUnauthorized) + return + } + + client, err := h.o.GetClient(auth.Username) + if err != nil { + pkg.HttpError(rw, errors.New("Unauthorized"), http.StatusUnauthorized) + return + } else if client.GetSecret() != auth.Password { + pkg.HttpError(rw, errors.New("Unauthorized"), http.StatusUnauthorized) + return + } + var p GrantedPayload decoder := json.NewDecoder(req.Body) if err := decoder.Decode(&p); err != nil { diff --git a/policy/handler/handler_test.go b/policy/handler/handler_test.go index 9210063c50d..ca794abcc66 100644 --- a/policy/handler/handler_test.go +++ b/policy/handler/handler_test.go @@ -2,6 +2,7 @@ package handler import ( "encoding/json" + "github.com/RangelReale/osin" "github.com/dgrijalva/jwt-go" "github.com/gorilla/mux" chd "github.com/ory-am/common/handler" @@ -13,6 +14,7 @@ import ( "github.com/ory-am/ladon/guard/operator" "github.com/ory-am/ladon/policy" "github.com/ory-am/ladon/policy/postgres" + opg "github.com/ory-am/osin-storage/storage/postgres" "github.com/parnurzeal/gorequest" "github.com/pborman/uuid" "github.com/stretchr/testify/require" @@ -28,6 +30,7 @@ import ( var ( mw *middleware.Middleware store *postgres.Store + o *opg.Storage ) var jwtService = hjwt.New([]byte(hjwt.TestCertificates[0][1]), []byte(hjwt.TestCertificates[1][1])) @@ -42,8 +45,19 @@ func TestMain(m *testing.M) { store = postgres.New(db) mw = &middleware.Middleware{} + o = opg.New(db) if err := store.CreateSchemas(); err != nil { - log.Fatalf("COuld not create schemas: %s", err) + log.Fatalf("Could not create schemas: %s", err) + } + if err := o.CreateSchemas(); err != nil { + log.Fatalf("Could not create schemas: %s", err) + } + + if err := o.CreateClient(&osin.DefaultClient{ + Id: "app", + Secret: "secret", + }); err != nil { + log.Fatalf("Could not create app: %s", err) } os.Exit(m.Run()) @@ -112,7 +126,7 @@ func TestGrantedEndpoint(t *testing.T) { policies: []policy.Policy{policies["pass-all"]}, } - handler := &Handler{s: store, m: mw, g: &guard.Guard{}, j: jwtService} + handler := &Handler{s: store, m: mw, g: &guard.Guard{}, j: jwtService, o: o} router := mux.NewRouter() handler.SetRoutes(router, mockAuthorization(c)) ts := httptest.NewServer(router) @@ -123,13 +137,17 @@ func TestGrantedEndpoint(t *testing.T) { require.Equal(t, 201, resp.StatusCode) num := 0 - do := func(p GrantedPayload, shouldAllow bool) { - resp, body, _ := request.Post(ts.URL + "/guard/allowed").Send(p).End() - require.Equal(t, 200, resp.StatusCode, "Case %d", num) - - var isAllowed struct { - Allowed bool `json:"allowed"` + var isAllowed struct { + Allowed bool `json:"allowed"` + } + do := func(p GrantedPayload, username, password string, shouldAllow bool) { + resp, body, _ := request.Post(ts.URL+"/guard/allowed").SetBasicAuth(username, password).Send(p).End() + if username != "app" || password != "secret" { + require.Equal(t, 401, resp.StatusCode, "Case %d", num) + return } + + require.Equal(t, 200, resp.StatusCode, "Case %d", num) json.Unmarshal([]byte(body), &isAllowed) require.Equal(t, 200, resp.StatusCode, "Case %d", num) require.Equal(t, shouldAllow, isAllowed.Allowed, "Case %d", num) @@ -144,7 +162,7 @@ func TestGrantedEndpoint(t *testing.T) { Context: &operator.Context{ Owner: "peter", }, - }, false) + }, "app", "secret", false) do(GrantedPayload{ Resource: "article", @@ -154,7 +172,27 @@ func TestGrantedEndpoint(t *testing.T) { Context: &operator.Context{ Owner: "peter", }, - }, true) + }, "app", "secret", true) + + do(GrantedPayload{ + Resource: "article", + // sub: peter + Token: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJwZXRlciIsImV4cCI6MTkyNDk3NTYxOX0.GVn0YAQTFFoIa-fcsqQgq3pgWBAYNsbd9SqoXUPt7EK63zqiZ0yVqWgQCBEXU5NyT96Alg1Se6Pq6wzAC4ydof-MN3nQhcoNhx6QEHBGFDwwsHwMVyi-51S0NXzYXSV-gGrPoOloCkOSoyab-RWdMZ6LrgV5WQOW4WAfYL0nJ0I-WxlXcoKi-8MJ1GqScqC_E0v9cn4iNAT5e1tPMT49KdjOo_HYPQlJQjcJ724USdDWywPxZy5AmYxG5A2XeaY41Ly0O0HJ8Q56I2ukPMfXiTpnm5mnb9mRbK99HnvlAvtEKJ-Lf0w_BTurL_3ZmONKSYR0HHIMZC0hO9NJNNTS1Q", + Permission: "foobar", + Context: &operator.Context{ + Owner: "peter", + }, + }, "app", "secret", true) + + do(GrantedPayload{ + Resource: "article", + // sub: peter + Token: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJwZXRlciIsImV4cCI6MTkyNDk3NTYxOX0.GVn0YAQTFFoIa-fcsqQgq3pgWBAYNsbd9SqoXUPt7EK63zqiZ0yVqWgQCBEXU5NyT96Alg1Se6Pq6wzAC4ydof-MN3nQhcoNhx6QEHBGFDwwsHwMVyi-51S0NXzYXSV-gGrPoOloCkOSoyab-RWdMZ6LrgV5WQOW4WAfYL0nJ0I-WxlXcoKi-8MJ1GqScqC_E0v9cn4iNAT5e1tPMT49KdjOo_HYPQlJQjcJ724USdDWywPxZy5AmYxG5A2XeaY41Ly0O0HJ8Q56I2ukPMfXiTpnm5mnb9mRbK99HnvlAvtEKJ-Lf0w_BTurL_3ZmONKSYR0HHIMZC0hO9NJNNTS1Q", + Permission: "foobar", + Context: &operator.Context{ + Owner: "peter", + }, + }, "foo", "secret", false) do(GrantedPayload{ Resource: "article", @@ -164,7 +202,7 @@ func TestGrantedEndpoint(t *testing.T) { Context: &operator.Context{ Owner: "peter", }, - }, true) + }, "app", "wrong-secret", false) do(GrantedPayload{ Resource: "foobar", @@ -174,7 +212,7 @@ func TestGrantedEndpoint(t *testing.T) { Context: &operator.Context{ Owner: "peter", }, - }, false) + }, "app", "secret", false) do(GrantedPayload{ Resource: "article", @@ -184,7 +222,7 @@ func TestGrantedEndpoint(t *testing.T) { Context: &operator.Context{ Owner: "peter", }, - }, false) + }, "app", "secret", false) } func TestCreateGetDeleteGet(t *testing.T) {