hydra icon indicating copy to clipboard operation
hydra copied to clipboard

[JWT Bearer Grant Type Issuers] Scopes are validated against first valid issuer for a specified subject in v2.X.X

Open StarAurryon opened this issue 1 year ago • 2 comments

Preflight checklist

Describe the bug

In Hydra v1.X.X if we issue two JWT Bearer Grant Type Issuers with 2 different keys for the same subject and same issuer but with scope "a" for the first key and scope "b", key 1 was allowed to grant scope "a" and key 2 was allowed to grant scope "b".

In Hydra v2.X.X if we do the same, key 1 is allowed to grant scope "a" and key 2 is not allowed to grant scope "b". If we delete Grant Issuer linked to key 1, then key 2 is allowed to grant scope "b".

Reproducing the bug

  1. Generate two JWT Bearer Grant Type Issuers with the same issuer, subject and scope a for Grant 1 and scope b for Grant 2.
  2. Request tokens with the scope specified in the JWT for each JWT with the client_id you created.
  3. Request with second JWT will fail saying scope b is not allowed.

Relevant log output

No response

Relevant configuration

No response

Version

v2.1.1

On which operating system are you observing this issue?

Linux

In which environment are you deploying?

Kubernetes

Additional Context

No response

StarAurryon avatar Apr 21 '23 13:04 StarAurryon

Found the culprit `func (p *Persister) GetPublicKeyScopes(ctx context.Context, issuer string, subject string, keyId string) ([]string, error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.GetPublicKeyScopes") defer span.End()

var data trust.SQLData
query := p.QueryWithNetwork(ctx).
	Where("issuer = ?", issuer).
	Where("subject = ? OR allow_any_subject IS TRUE", subject).
	Where("key_id = ?", keyId).
	Where("nid = ?", p.NetworkID(ctx))

if err := query.First(&data); err != nil {
	return nil, sqlcon.HandleError(err)
}

return p.jwtGrantFromSQlData(data).Scope, nil

}`

Where("subject = ? OR allow_any_subject IS TRUE", subject). should be Where("(subject = ? OR allow_any_subject IS TRUE)", subject).

StarAurryon avatar Apr 21 '23 14:04 StarAurryon

Small demo code :

package main
import (
	"context"
	"fmt"
	"time"

	"github.com/gobuffalo/pop/v6"
	"github.com/xtgo/uuid"
)

type SQLData struct {
	ID              string    `db:"id"`
	NID             uuid.UUID `db:"nid"`
	Issuer          string    `db:"issuer"`
	Subject         string    `db:"subject"`
	AllowAnySubject bool      `db:"allow_any_subject"`
	Scope           string    `db:"scope"`
	KeySet          string    `db:"key_set"`
	KeyID           string    `db:"key_id"`
	CreatedAt       time.Time `db:"created_at"`
	ExpiresAt       time.Time `db:"expires_at"`
}

func main() {
	con, err := pop.Connect("development")
	if err != nil {
		panic(err)
	}

	model := pop.NewModel(new(SQLData), context.Background())

	query := con.Q().Where("issuer = ?", "abc").
		Where("subject = ? OR allow_any_subject IS TRUE", "def").
		Where("key_id = ?", "ghi").
		Where("nid = ?", "klm")

	fmt.Println(query.Exec())

	str, _ := query.ToSQL(model)
	fmt.Println(str)

}

Output:

SELECT sql_data.allow_any_subject, sql_data.created_at, sql_data.expires_at, sql_data.id, sql_data.issuer, sql_data.key_id, sql_data.key_set, sql_data.nid, sql_data.scope, sql_data.subject FROM sql_data AS sql_data WHERE issuer = ? AND subject = ? OR allow_any_subject IS TRUE AND key_id = ? AND nid = ?

StarAurryon avatar Apr 21 '23 15:04 StarAurryon