Skip to content

Commit

Permalink
Follow gophercloud upstream and remove packed errors
Browse files Browse the repository at this point in the history
  • Loading branch information
SuperSandro2000 committed Jul 1, 2024
1 parent ccc1f6a commit 3996739
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 46 deletions.
51 changes: 29 additions & 22 deletions gopherpolicy/pkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,8 @@ import (
// test double (such as type mock.Validator).
type Validator interface {
// CheckToken checks the validity of the request's X-Auth-Token in Keystone, and
// returns a Token instance for checking authorization. Any errors that occur
// during this function are deferred until Token.Require() is called.
CheckToken(r *http.Request) *Token
// returns a Token instance for checking authorization.
CheckToken(r *http.Request) (*Token, error)
}

// Cacher is the generic interface for a token cache.
Expand Down Expand Up @@ -89,21 +88,26 @@ func (v *TokenValidator) LoadPolicyFile(path string) error {
}

// CheckToken checks the validity of the request's X-Auth-Token in Keystone, and
// returns a Token instance for checking authorization. Any errors that occur
// during this function are deferred until Require() is called.
func (v *TokenValidator) CheckToken(r *http.Request) *Token {
// returns a Token instance for checking authorization.
// The suggested HTTP status code on errors is 401 Unauthoirzed.
func (v *TokenValidator) CheckToken(r *http.Request) (*Token, string, error) {
tokenStr := r.Header.Get("X-Auth-Token")
if tokenStr == "" {
return &Token{Err: errors.New("X-Auth-Token header missing")}
return nil, "", errors.New("X-Auth-Token header missing")
}

token := v.CheckCredentials(tokenStr, func() TokenResult {
return tokens.Get(r.Context(), v.IdentityV3, tokenStr)
token, retryAfterStr, err := v.CheckCredentials(tokenStr, func() (TokenResult, http.Header) {
tokenResult := tokens.Get(r.Context(), v.IdentityV3, tokenStr)
return tokenResult, tokenResult.Header
})
if err != nil {
return nil, retryAfterStr, errors.New("Unauthorized")
}

token.Context.Logger = logg.Debug
logg.Debug("token has auth = %v", token.Context.Auth)
logg.Debug("token has roles = %v", token.Context.Roles)
return token
return token, "", nil
}

// CheckCredentials is a more generic version of CheckToken that can also be
Expand All @@ -116,7 +120,8 @@ func (v *TokenValidator) CheckToken(r *http.Request) *Token {
// The `cacheKey` argument shall be a string that identifies the given
// credentials. This key is used for caching the TokenResult in `v.Cacher` if
// that is non-nil.
func (v *TokenValidator) CheckCredentials(cacheKey string, check func() TokenResult) *Token {
func (v *TokenValidator) CheckCredentials(cacheKey string, check func() (TokenResult, http.Header)) (*Token, string, error) {
var retryAfterStr string
// prefer cached token payload over actually talking to Keystone (but fallback
// to Keystone if the token payload deserialization fails)
if v.Cacher != nil {
Expand All @@ -125,44 +130,46 @@ func (v *TokenValidator) CheckCredentials(cacheKey string, check func() TokenRes
var s serializableToken
err := json.Unmarshal(payload, &s)
if err == nil && s.Token.ExpiresAt.After(time.Now()) {
t := v.TokenFromGophercloudResult(s)
if t.Err == nil {
return t
var t *Token
t, retryAfterStr, err = v.TokenFromGophercloudResult(s, http.Header{})
if err == nil {
return t, retryAfterStr, nil
}
}
}
}

t := v.TokenFromGophercloudResult(check())
t, retryAfterStr, err := v.TokenFromGophercloudResult(check())

// cache token payload if valid
if t.Err == nil && v.Cacher != nil {
if err == nil && v.Cacher != nil {
payload, err := json.Marshal(t.serializable)
if err == nil {
v.Cacher.StoreTokenPayload(cacheKey, payload)
}
}

return t
return t, retryAfterStr, err
}

// TokenFromGophercloudResult creates a Token instance from a gophercloud Result
// from the tokens.Create() or tokens.Get() requests from package
// github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens.
func (v *TokenValidator) TokenFromGophercloudResult(result TokenResult) *Token {
func (v *TokenValidator) TokenFromGophercloudResult(result TokenResult, header http.Header) (*Token, string, error) {
// use a custom token struct instead of tokens.Token which is way incomplete
var tokenData keystoneToken
retryAfterStr := header.Get("Retry-After")
err := result.ExtractInto(&tokenData)
if err != nil {
return &Token{Err: err}
return nil, retryAfterStr, err
}
token, err := result.Extract()
if err != nil {
return &Token{Err: err}
return nil, retryAfterStr, err
}
catalog, err := result.ExtractServiceCatalog()
if err != nil {
return &Token{Err: err}
return nil, retryAfterStr, err
}

return &Token{
Expand All @@ -183,7 +190,7 @@ func (v *TokenValidator) TokenFromGophercloudResult(result TokenResult) *Token {
TokenData: tokenData,
ServiceCatalog: catalog.Entries,
},
}
}, "", nil
}

// TokenResult is the interface type for the argument of
Expand Down
26 changes: 3 additions & 23 deletions gopherpolicy/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ package gopherpolicy

import (
"fmt"
"net/http"

policy "github.com/databus23/goslo.policy"
"github.com/gophercloud/gophercloud/v2"
Expand All @@ -48,36 +47,17 @@ type Token struct {
// When AuthN succeeds, contains a fully-initialized ProviderClient with which
// this process can use the OpenStack API on behalf of the authenticated user.
ProviderClient *gophercloud.ProviderClient
// When AuthN fails, contains the deferred AuthN error.
Err error

// When AuthN succeeds, contains all the information needed to serialize this
// token in SerializeTokenForCache.
serializable serializableToken
}

// Require checks if the given token has the given permission according to the
// policy.json that is in effect. If not, an error response is written and false
// is returned.
func (t *Token) Require(w http.ResponseWriter, rule string) bool {
if t.Err != nil {
if t.Context.Logger != nil {
t.Context.Logger(fmt.Sprintf("returning %v because of error: %s", http.StatusUnauthorized, t.Err.Error()))
}
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return false
}

if !t.Enforcer.Enforce(rule, t.Context) {
http.Error(w, "Forbidden", http.StatusForbidden)
return false
}
return true
}

// Check is like Require, but does not write error responses.
// policy.json that is in effect. If not false is returned.
// The suggested HTTP Status code for false is 403 Forbidden.
func (t *Token) Check(rule string) bool {
return t.Err == nil && t.Enforcer.Enforce(rule, t.Context)
return t.Enforcer.Enforce(rule, t.Context)
}

// UserUUID returns the UUID of the user for whom this token was issued, or ""
Expand Down
3 changes: 2 additions & 1 deletion mock/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ func TestValidator(t *testing.T) {

// setup a simple HTTP handler that just outputs status 204, 401 or 403 depending on auth result
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !v.CheckToken(r).Require(w, "api:access") {
if !v.CheckToken(r).Check("api:access") {
w.WriteHeader(http.StatusForbidden)
return
}
w.WriteHeader(http.StatusNoContent)
Expand Down

0 comments on commit 3996739

Please sign in to comment.