Initial QSfera import
This commit is contained in:
+16
@@ -0,0 +1,16 @@
|
||||
load("@rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "errors",
|
||||
srcs = [
|
||||
"errors.go",
|
||||
],
|
||||
importpath = "github.com/lestrrat-go/jwx/v3/jwt/internal/errors",
|
||||
visibility = ["//jwt:__subpackages__"],
|
||||
)
|
||||
|
||||
alias(
|
||||
name = "go_default_library",
|
||||
actual = ":errors",
|
||||
visibility = ["//jwt:__subpackages__"],
|
||||
)
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
// Package errors exist to store errors for jwt and openid packages.
|
||||
//
|
||||
// It's internal because we don't want to expose _anything_ about these errors
|
||||
// so users absolutely cannot do anything other than use them as opaque errors.
|
||||
//
|
||||
//nolint:revive
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrClaimNotFound = ClaimNotFoundError{}
|
||||
ErrClaimAssignmentFailed = ClaimAssignmentFailedError{Err: errors.New(`claim assignment failed`)}
|
||||
ErrUnknownPayloadType = errors.New(`unknown payload type (payload is not JWT?)`)
|
||||
ErrParse = ParseError{error: errors.New(`jwt.Parse: unknown error`)}
|
||||
ErrValidateDefault = ValidationError{errors.New(`unknown error`)}
|
||||
ErrInvalidIssuerDefault = InvalidIssuerError{errors.New(`"iss" not satisfied`)}
|
||||
ErrTokenExpiredDefault = TokenExpiredError{errors.New(`"exp" not satisfied: token is expired`)}
|
||||
ErrInvalidIssuedAtDefault = InvalidIssuedAtError{errors.New(`"iat" not satisfied`)}
|
||||
ErrTokenNotYetValidDefault = TokenNotYetValidError{errors.New(`"nbf" not satisfied: token is not yet valid`)}
|
||||
ErrInvalidAudienceDefault = InvalidAudienceError{errors.New(`"aud" not satisfied`)}
|
||||
ErrMissingRequiredClaimDefault = &MissingRequiredClaimError{error: errors.New(`required claim is missing`)}
|
||||
)
|
||||
|
||||
type ClaimNotFoundError struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (e ClaimNotFoundError) Error() string {
|
||||
// This error message uses "field" instead of "claim" for backwards compatibility,
|
||||
// but it shuold really be "claim" since it refers to a JWT claim.
|
||||
return fmt.Sprintf(`field "%s" not found`, e.Name)
|
||||
}
|
||||
|
||||
func (e ClaimNotFoundError) Is(target error) bool {
|
||||
_, ok := target.(ClaimNotFoundError)
|
||||
return ok
|
||||
}
|
||||
|
||||
type ClaimAssignmentFailedError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e ClaimAssignmentFailedError) Error() string {
|
||||
// This error message probably should be tweaked, but it is this way
|
||||
// for backwards compatibility.
|
||||
return fmt.Sprintf(`failed to assign value to dst: %s`, e.Err.Error())
|
||||
}
|
||||
|
||||
func (e ClaimAssignmentFailedError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
func (e ClaimAssignmentFailedError) Is(target error) bool {
|
||||
_, ok := target.(ClaimAssignmentFailedError)
|
||||
return ok
|
||||
}
|
||||
|
||||
type ParseError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (e ParseError) Unwrap() error {
|
||||
return e.error
|
||||
}
|
||||
|
||||
func (ParseError) Is(err error) bool {
|
||||
_, ok := err.(ParseError)
|
||||
return ok
|
||||
}
|
||||
|
||||
func ParseErrorf(prefix, f string, args ...any) error {
|
||||
return ParseError{fmt.Errorf(prefix+": "+f, args...)}
|
||||
}
|
||||
|
||||
type ValidationError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (ValidationError) Is(err error) bool {
|
||||
_, ok := err.(ValidationError)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ValidationError) Unwrap() error {
|
||||
return err.error
|
||||
}
|
||||
|
||||
func ValidateErrorf(f string, args ...any) error {
|
||||
return ValidationError{fmt.Errorf(`jwt.Validate: `+f, args...)}
|
||||
}
|
||||
|
||||
type InvalidIssuerError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (err InvalidIssuerError) Is(target error) bool {
|
||||
_, ok := target.(InvalidIssuerError)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err InvalidIssuerError) Unwrap() error {
|
||||
return err.error
|
||||
}
|
||||
|
||||
func IssuerErrorf(f string, args ...any) error {
|
||||
return InvalidIssuerError{fmt.Errorf(`"iss" not satisfied: `+f, args...)}
|
||||
}
|
||||
|
||||
type TokenExpiredError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (err TokenExpiredError) Is(target error) bool {
|
||||
_, ok := target.(TokenExpiredError)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err TokenExpiredError) Unwrap() error {
|
||||
return err.error
|
||||
}
|
||||
|
||||
type InvalidIssuedAtError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (err InvalidIssuedAtError) Is(target error) bool {
|
||||
_, ok := target.(InvalidIssuedAtError)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err InvalidIssuedAtError) Unwrap() error {
|
||||
return err.error
|
||||
}
|
||||
|
||||
type TokenNotYetValidError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (err TokenNotYetValidError) Is(target error) bool {
|
||||
_, ok := target.(TokenNotYetValidError)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err TokenNotYetValidError) Unwrap() error {
|
||||
return err.error
|
||||
}
|
||||
|
||||
type InvalidAudienceError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (err InvalidAudienceError) Is(target error) bool {
|
||||
_, ok := target.(InvalidAudienceError)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err InvalidAudienceError) Unwrap() error {
|
||||
return err.error
|
||||
}
|
||||
|
||||
func AudienceErrorf(f string, args ...any) error {
|
||||
return InvalidAudienceError{fmt.Errorf(`"aud" not satisfied: `+f, args...)}
|
||||
}
|
||||
|
||||
type MissingRequiredClaimError struct {
|
||||
error
|
||||
|
||||
claim string
|
||||
}
|
||||
|
||||
func (err *MissingRequiredClaimError) Is(target error) bool {
|
||||
err1, ok := target.(*MissingRequiredClaimError)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return err1 == ErrMissingRequiredClaimDefault || err1.claim == err.claim
|
||||
}
|
||||
|
||||
func MissingRequiredClaimErrorf(name string) error {
|
||||
return &MissingRequiredClaimError{claim: name, error: fmt.Errorf(`required claim "%s" is missing`, name)}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
load("@rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "types",
|
||||
srcs = [
|
||||
"date.go",
|
||||
"string.go",
|
||||
],
|
||||
importpath = "github.com/lestrrat-go/jwx/v3/jwt/internal/types",
|
||||
visibility = ["//jwt:__subpackages__"],
|
||||
deps = [
|
||||
"//internal/json",
|
||||
"//internal/tokens",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "types_test",
|
||||
srcs = [
|
||||
"date_test.go",
|
||||
"string_test.go",
|
||||
],
|
||||
deps = [
|
||||
":types",
|
||||
"//internal/json",
|
||||
"//jwt",
|
||||
"@com_github_stretchr_testify//require",
|
||||
],
|
||||
)
|
||||
|
||||
alias(
|
||||
name = "go_default_library",
|
||||
actual = ":types",
|
||||
visibility = ["//jwt:__subpackages__"],
|
||||
)
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/json"
|
||||
"github.com/lestrrat-go/jwx/v3/internal/tokens"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultPrecision uint32 = 0 // second level
|
||||
MaxPrecision uint32 = 9 // nanosecond level
|
||||
)
|
||||
|
||||
var Pedantic uint32
|
||||
var ParsePrecision = DefaultPrecision
|
||||
var FormatPrecision = DefaultPrecision
|
||||
|
||||
// NumericDate represents the date format used in the 'nbf' claim
|
||||
type NumericDate struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func (n *NumericDate) Get() time.Time {
|
||||
if n == nil {
|
||||
return (time.Time{}).UTC()
|
||||
}
|
||||
return n.Time
|
||||
}
|
||||
|
||||
func intToTime(v any, t *time.Time) bool {
|
||||
var n int64
|
||||
switch x := v.(type) {
|
||||
case int64:
|
||||
n = x
|
||||
case int32:
|
||||
n = int64(x)
|
||||
case int16:
|
||||
n = int64(x)
|
||||
case int8:
|
||||
n = int64(x)
|
||||
case int:
|
||||
n = int64(x)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
*t = time.Unix(n, 0)
|
||||
return true
|
||||
}
|
||||
|
||||
func parseNumericString(x string) (time.Time, error) {
|
||||
var t time.Time // empty time for empty return value
|
||||
|
||||
// Only check for the escape hatch if it's the pedantic
|
||||
// flag is off
|
||||
if Pedantic != 1 {
|
||||
// This is an escape hatch for non-conformant providers
|
||||
// that gives us RFC3339 instead of epoch time
|
||||
for _, r := range x {
|
||||
// 0x30 = '0', 0x39 = '9', 0x2E = tokens.Period
|
||||
if (r >= 0x30 && r <= 0x39) || r == 0x2E {
|
||||
continue
|
||||
}
|
||||
|
||||
// if it got here, then it probably isn't epoch time
|
||||
tv, err := time.Parse(time.RFC3339, x)
|
||||
if err != nil {
|
||||
return t, fmt.Errorf(`value is not number of seconds since the epoch, and attempt to parse it as RFC3339 timestamp failed: %w`, err)
|
||||
}
|
||||
return tv, nil
|
||||
}
|
||||
}
|
||||
|
||||
var fractional string
|
||||
whole := x
|
||||
if i := strings.IndexRune(x, tokens.Period); i > 0 {
|
||||
if ParsePrecision > 0 && len(x) > i+1 {
|
||||
fractional = x[i+1:] // everything after the tokens.Period
|
||||
if int(ParsePrecision) < len(fractional) {
|
||||
// Remove insignificant digits
|
||||
fractional = fractional[:int(ParsePrecision)]
|
||||
}
|
||||
// Replace missing fractional diits with zeros
|
||||
for len(fractional) < int(MaxPrecision) {
|
||||
fractional = fractional + "0"
|
||||
}
|
||||
}
|
||||
whole = x[:i]
|
||||
}
|
||||
n, err := strconv.ParseInt(whole, 10, 64)
|
||||
if err != nil {
|
||||
return t, fmt.Errorf(`failed to parse whole value %q: %w`, whole, err)
|
||||
}
|
||||
var nsecs int64
|
||||
if fractional != "" {
|
||||
v, err := strconv.ParseInt(fractional, 10, 64)
|
||||
if err != nil {
|
||||
return t, fmt.Errorf(`failed to parse fractional value %q: %w`, fractional, err)
|
||||
}
|
||||
nsecs = v
|
||||
}
|
||||
|
||||
return time.Unix(n, nsecs).UTC(), nil
|
||||
}
|
||||
|
||||
func (n *NumericDate) Accept(v any) error {
|
||||
var t time.Time
|
||||
switch x := v.(type) {
|
||||
case float32:
|
||||
tv, err := parseNumericString(fmt.Sprintf(`%.9f`, x))
|
||||
if err != nil {
|
||||
return fmt.Errorf(`failed to accept float32 %.9f: %w`, x, err)
|
||||
}
|
||||
t = tv
|
||||
case float64:
|
||||
tv, err := parseNumericString(fmt.Sprintf(`%.9f`, x))
|
||||
if err != nil {
|
||||
return fmt.Errorf(`failed to accept float32 %.9f: %w`, x, err)
|
||||
}
|
||||
t = tv
|
||||
case json.Number:
|
||||
tv, err := parseNumericString(x.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf(`failed to accept json.Number %q: %w`, x.String(), err)
|
||||
}
|
||||
t = tv
|
||||
case string:
|
||||
tv, err := parseNumericString(x)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`failed to accept string %q: %w`, x, err)
|
||||
}
|
||||
t = tv
|
||||
case time.Time:
|
||||
t = x
|
||||
default:
|
||||
if !intToTime(v, &t) {
|
||||
return fmt.Errorf(`invalid type %T`, v)
|
||||
}
|
||||
}
|
||||
n.Time = t.UTC()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n NumericDate) String() string {
|
||||
if FormatPrecision == 0 {
|
||||
return strconv.FormatInt(n.Unix(), 10)
|
||||
}
|
||||
|
||||
// This is cheating, but it's better (easier) than doing floating point math
|
||||
// We basically munge with strings after formatting an integer value
|
||||
// for nanoseconds since epoch
|
||||
s := strconv.FormatInt(n.UnixNano(), 10)
|
||||
for len(s) < int(MaxPrecision) {
|
||||
s = "0" + s
|
||||
}
|
||||
|
||||
slwhole := len(s) - int(MaxPrecision)
|
||||
s = s[:slwhole] + "." + s[slwhole:slwhole+int(FormatPrecision)]
|
||||
if s[0] == tokens.Period {
|
||||
s = "0" + s
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// MarshalJSON translates from internal representation to JSON NumericDate
|
||||
// See https://tools.ietf.org/html/rfc7519#page-6
|
||||
func (n *NumericDate) MarshalJSON() ([]byte, error) {
|
||||
if n.IsZero() {
|
||||
return json.Marshal(nil)
|
||||
}
|
||||
|
||||
return json.Marshal(n.String())
|
||||
}
|
||||
|
||||
func (n *NumericDate) UnmarshalJSON(data []byte) error {
|
||||
var v any
|
||||
if err := json.Unmarshal(data, &v); err != nil {
|
||||
return fmt.Errorf(`failed to unmarshal date: %w`, err)
|
||||
}
|
||||
|
||||
var n2 NumericDate
|
||||
if err := n2.Accept(v); err != nil {
|
||||
return fmt.Errorf(`invalid value for NumericDate: %w`, err)
|
||||
}
|
||||
*n = n2
|
||||
return nil
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/json"
|
||||
)
|
||||
|
||||
type StringList []string
|
||||
|
||||
func (l StringList) Get() []string {
|
||||
return []string(l)
|
||||
}
|
||||
|
||||
func (l *StringList) Accept(v any) error {
|
||||
switch x := v.(type) {
|
||||
case string:
|
||||
*l = StringList([]string{x})
|
||||
case []string:
|
||||
*l = StringList(x)
|
||||
case []any:
|
||||
list := make(StringList, len(x))
|
||||
for i, e := range x {
|
||||
if s, ok := e.(string); ok {
|
||||
list[i] = s
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf(`invalid list element type %T`, e)
|
||||
}
|
||||
*l = list
|
||||
default:
|
||||
return fmt.Errorf(`invalid type: %T`, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *StringList) UnmarshalJSON(data []byte) error {
|
||||
var v any
|
||||
if err := json.Unmarshal(data, &v); err != nil {
|
||||
return fmt.Errorf(`failed to unmarshal data: %w`, err)
|
||||
}
|
||||
return l.Accept(v)
|
||||
}
|
||||
Reference in New Issue
Block a user