~bigbes/core-go

2d9a547c395c1e82ddde6ed5c47312037a07996a — Drew DeVault 4 years ago cdb177b
auth: harden grants structure

This replaces the Access map with a data type whose default value does
not grant any permissions, so that it's more difficult to create an
insecure auth context by mistake.
3 files changed, 51 insertions(+), 15 deletions(-)

M auth/bearer.go
M auth/middleware.go
M server/directives.go
M auth/bearer.go => auth/bearer.go +45 -3
@@ 80,10 80,25 @@ func DecodeBearerToken(token string) *BearerToken {
	return &bt
}

func DecodeGrants(ctx context.Context, grants string) map[string]string {
const (
	RO = "RO"
	RW = "RW"
)

type Grants struct {
	all     bool
	grants  map[string]string
	encoded string
}

func DecodeGrants(ctx context.Context, grants string) Grants {
	if grants == "" {
		// All permissions
		return nil
		return Grants{
			all:     true,
			grants:  nil,
			encoded: "",
		}
	}
	accessMap := make(map[string]string)
	for _, grant := range strings.Split(grants, " ") {


@@ 108,5 123,32 @@ func DecodeGrants(ctx context.Context, grants string) map[string]string {
			accessMap[scope] = access
		}
	}
	return accessMap
	return Grants{
		all:     false,
		grants:  accessMap,
		encoded: grants,
	}
}

func (g *Grants) Has(grant string, mode string) bool {
	if mode != RO && mode != RW {
		panic("Invalid access mode")
	}

	if g.all {
		return true
	}

	if access, ok := g.grants[grant]; !ok {
		return false
	} else {
		if mode == RO {
			return true
		}
		return mode == access
	}
}

func (g *Grants) Encode() string {
	return g.encoded
}

M auth/middleware.go => auth/middleware.go +3 -3
@@ 77,7 77,7 @@ type AuthContext struct {

	// Only filled out if AuthMethod == AUTH_OAUTH2 or AUTH_WEBHOOK
	BearerToken *BearerToken
	Access      map[string]string
	Grants      Grants
	TokenHash   [64]byte
}



@@ 554,7 554,7 @@ func OAuth2(token string, hash [64]byte, w http.ResponseWriter,
	auth.AuthMethod = AUTH_OAUTH2
	auth.BearerToken = bt
	auth.TokenHash = hash
	auth.Access = DecodeGrants(r.Context(), bt.Grants)
	auth.Grants = DecodeGrants(r.Context(), bt.Grants)

	ctx := context.WithValue(r.Context(), userCtxKey, &auth)
	r = r.WithContext(ctx)


@@ 666,7 666,7 @@ func WebhookAuth(ctx context.Context, auth *AuthContext,
	whAuth := *auth
	whAuth.AuthMethod = AUTH_WEBHOOK
	whAuth.TokenHash = tokenHash
	whAuth.Access = DecodeGrants(ctx, grants)
	whAuth.Grants = DecodeGrants(ctx, grants)
	whAuth.BearerToken = &BearerToken{}
	if clientID != nil {
		whAuth.BearerToken.ClientID = *clientID

M server/directives.go => server/directives.go +3 -9
@@ 44,25 44,19 @@ func Access(ctx context.Context, obj interface{}, next graphql.Resolver,
	case auth.AUTH_INTERNAL, auth.AUTH_COOKIE:
		return next(ctx)
	case auth.AUTH_OAUTH_LEGACY:
		if kind == "RO" {
		if kind == auth.RO {
			// Only legacy tokens with "*" scopes ever get this far
			return next(ctx)
		}
	case auth.AUTH_WEBHOOK:
		if kind != "RO" {
		if kind != auth.RO {
			return nil, fmt.Errorf("Access to read/write resolver denied for webhook")
		}
		fallthrough
	case auth.AUTH_OAUTH2:
		if authctx.Access == nil {
		if authctx.Grants.Has(scope, kind) {
			return next(ctx)
		}
		if access, ok := authctx.Access[scope]; !ok {
			break
		} else if access == "RO" && kind == "RW" {
			break
		}
		return next(ctx)
	default:
		panic(fmt.Errorf("Unknown auth method for access check"))
	}