jwt profile and authorization handling

This commit is contained in:
Livio Amstutz 2020-09-28 13:55:22 +02:00
parent d368b2d950
commit 0cad2e4652
12 changed files with 128 additions and 309 deletions

View file

@ -1,5 +1,9 @@
package tokenexchange
import (
"github.com/caos/oidc/pkg/oidc"
)
const (
AccessTokenType = "urn:ietf:params:oauth:token-type:access_token"
RefreshTokenType = "urn:ietf:params:oauth:token-type:refresh_token"
@ -23,7 +27,19 @@ type TokenExchangeRequest struct {
}
type JWTProfileRequest struct {
Assertion string `schema:"assertion"`
Assertion string `schema:"assertion"`
Scope oidc.Scopes `schema:"scope"`
GrantType oidc.GrantType `schema:"grant_type"`
}
//ClientCredentialsGrantBasic creates an oauth2 `Client Credentials` Grant
//sneding client_id and client_secret as basic auth header
func NewJWTProfileRequest(assertion string, scopes ...string) *JWTProfileRequest {
return &JWTProfileRequest{
GrantType: oidc.GrantTypeBearer,
Assertion: assertion,
Scope: scopes,
}
}
func NewTokenExchangeRequest(subjectToken, subjectTokenType string, opts ...TokenExchangeOption) *TokenExchangeRequest {

View file

@ -202,7 +202,6 @@ type AccessTokenResponse struct {
type JWTProfileAssertion struct {
PrivateKeyID string `json:"-"`
PrivateKey []byte `json:"-"`
Scopes []string `json:"scopes"`
Issuer string `json:"issuer"`
Subject string `json:"sub"`
Audience Audience `json:"aud"`
@ -236,7 +235,6 @@ func NewJWTProfileAssertion(userID, keyID string, audience []string, key []byte)
PrivateKey: key,
PrivateKeyID: keyID,
Issuer: userID,
Scopes: []string{ScopeOpenID},
Subject: userID,
IssuedAt: Time(time.Now().UTC()),
Expiration: Time(time.Now().Add(1 * time.Hour).UTC()),
@ -244,80 +242,6 @@ func NewJWTProfileAssertion(userID, keyID string, audience []string, key []byte)
}
}
//
//type jsonToken struct {
// Issuer string `json:"iss,omitempty"`
// Subject string `json:"sub,omitempty"`
// Audiences interface{} `json:"aud,omitempty"`
// Expiration int64 `json:"exp,omitempty"`
// NotBefore int64 `json:"nbf,omitempty"`
// IssuedAt int64 `json:"iat,omitempty"`
// JWTID string `json:"jti,omitempty"`
// AuthorizedParty string `json:"azp,omitempty"`
// Nonce string `json:"nonce,omitempty"`
// AuthTime int64 `json:"auth_time,omitempty"`
// AccessTokenHash string `json:"at_hash,omitempty"`
// CodeHash string `json:"c_hash,omitempty"`
// AuthenticationContextClassReference string `json:"acr,omitempty"`
// AuthenticationMethodsReferences []string `json:"amr,omitempty"`
// SessionID string `json:"sid,omitempty"`
// Actor interface{} `json:"act,omitempty"` //TODO: impl
// Scopes string `json:"scope,omitempty"`
// ClientID string `json:"client_id,omitempty"`
// AuthorizedActor interface{} `json:"may_act,omitempty"` //TODO: impl
// AccessTokenUseNumber int `json:"at_use_nbr,omitempty"`
// jsonUserinfo
//}
//
//func (t *accessTokenClaims) MarshalJSON() ([]byte, error) {
// j := jsonToken{
// Issuer: t.Issuer,
// Subject: t.Subject,
// Audiences: t.Audiences,
// Expiration: timeToJSON(t.Expiration),
// NotBefore: timeToJSON(t.NotBefore),
// IssuedAt: timeToJSON(t.IssuedAt),
// JWTID: t.JWTID,
// AuthorizedParty: t.AuthorizedParty,
// Nonce: t.Nonce,
// AuthTime: timeToJSON(t.AuthTime),
// CodeHash: t.CodeHash,
// AuthenticationContextClassReference: t.AuthenticationContextClassReference,
// AuthenticationMethodsReferences: t.AuthenticationMethodsReferences,
// SessionID: t.SessionID,
// Scopes: strings.Join(t.Scopes, " "),
// ClientID: t.ClientID,
// AccessTokenUseNumber: t.AccessTokenUseNumber,
// }
// return json.Marshal(j)
//}
//
//func (t *accessTokenClaims) UnmarshalJSON(b []byte) error {
// var j jsonToken
// if err := json.Unmarshal(b, &j); err != nil {
// return err
// }
// t.Issuer = j.Issuer
// t.Subject = j.Subject
// t.Audiences = audienceFromJSON(j.Audiences)
// t.Expiration = time.Unix(j.Expiration, 0).UTC()
// t.NotBefore = time.Unix(j.NotBefore, 0).UTC()
// t.IssuedAt = time.Unix(j.IssuedAt, 0).UTC()
// t.JWTID = j.JWTID
// t.AuthorizedParty = j.AuthorizedParty
// t.Nonce = j.Nonce
// t.AuthTime = time.Unix(j.AuthTime, 0).UTC()
// t.CodeHash = j.CodeHash
// t.AuthenticationContextClassReference = j.AuthenticationContextClassReference
// t.AuthenticationMethodsReferences = j.AuthenticationMethodsReferences
// t.SessionID = j.SessionID
// t.Scopes = strings.Split(j.Scopes, " ")
// t.ClientID = j.ClientID
// t.AccessTokenUseNumber = j.AccessTokenUseNumber
// return nil
//}
//
func (t *idTokenClaims) MarshalJSON() ([]byte, error) {
type Alias idTokenClaims
a := &struct {
@ -406,84 +330,6 @@ func (t *idTokenClaims) SetSignatureAlgorithm(alg jose.SignatureAlgorithm) {
t.signatureAlg = alg
}
//
//func (t *JWTProfileAssertion) MarshalJSON() ([]byte, error) {
// j := jsonToken{
// Issuer: t.Issuer,
// Subject: t.Subject,
// Audiences: t.Audience,
// Expiration: timeToJSON(t.Expiration),
// IssuedAt: timeToJSON(t.IssuedAt),
// Scopes: strings.Join(t.Scopes, " "),
// }
// return json.Marshal(j)
//}
//func (t *JWTProfileAssertion) UnmarshalJSON(b []byte) error {
// var j jsonToken
// if err := json.Unmarshal(b, &j); err != nil {
// return err
// }
//
// t.Issuer = j.Issuer
// t.Subject = j.Subject
// t.Audience = audienceFromJSON(j.Audiences)
// t.Expiration = time.Unix(j.Expiration, 0).UTC()
// t.IssuedAt = time.Unix(j.IssuedAt, 0).UTC()
// t.Scopes = strings.Split(j.Scopes, " ")
//
// return nil
//}
//
//func (j *jsonToken) UnmarshalUserinfoProfile() userInfoProfile {
// locale, _ := language.Parse(j.Locale)
// return userInfoProfile{
// Name: j.Name,
// GivenName: j.GivenName,
// FamilyName: j.FamilyName,
// MiddleName: j.MiddleName,
// Nickname: j.Nickname,
// Profile: j.Profile,
// Picture: j.Picture,
// Website: j.Website,
// Gender: Gender(j.Gender),
// Birthdate: j.Birthdate,
// Zoneinfo: j.Zoneinfo,
// Locale: locale,
// UpdatedAt: time.Unix(j.UpdatedAt, 0).UTC(),
// PreferredUsername: j.PreferredUsername,
// }
//}
//
//func (j *jsonToken) UnmarshalUserinfoEmail() userInfoEmail {
// return userInfoEmail{
// Email: j.Email,
// EmailVerified: j.EmailVerified,
// }
//}
//
//func (j *jsonToken) UnmarshalUserinfoPhone() userInfoPhone {
// return userInfoPhone{
// PhoneNumber: j.Phone,
// PhoneNumberVerified: j.PhoneVerified,
// }
//}
//
//func (j *jsonToken) UnmarshalUserinfoAddress() *UserinfoAddress {
// if j.JsonUserinfoAddress == nil {
// return nil
// }
// return &UserinfoAddress{
// Country: j.JsonUserinfoAddress.Country,
// Formatted: j.JsonUserinfoAddress.Formatted,
// Locality: j.JsonUserinfoAddress.Locality,
// PostalCode: j.JsonUserinfoAddress.PostalCode,
// Region: j.JsonUserinfoAddress.Region,
// StreetAddress: j.JsonUserinfoAddress.StreetAddress,
// }
//}
func ClaimHash(claim string, sigAlgorithm jose.SignatureAlgorithm) (string, error) {
hash, err := utils.GetHashAlgorithm(sigAlgorithm)
if err != nil {
@ -492,26 +338,3 @@ func ClaimHash(claim string, sigAlgorithm jose.SignatureAlgorithm) (string, erro
return utils.HashString(hash, claim, true), nil
}
func timeToJSON(t time.Time) int64 {
if t.IsZero() {
return 0
}
return t.Unix()
}
func audienceFromJSON(i interface{}) []string {
switch aud := i.(type) {
case []string:
return aud
case []interface{}:
audience := make([]string, len(aud))
for i, a := range aud {
audience[i] = a.(string)
}
return audience
case string:
return []string{aud}
}
return nil
}

View file

@ -37,7 +37,7 @@ func (a *AccessTokenRequest) GrantType() GrantType {
type JWTTokenRequest struct {
Issuer string `json:"iss"`
Subject string `json:"sub"`
Scopes Scopes `json:"scope"`
Scopes Scopes `json:"-"`
Audience Audience `json:"aud"`
IssuedAt Time `json:"iat"`
ExpiresAt Time `json:"exp"`

View file

@ -41,38 +41,6 @@ func (d *Display) UnmarshalText(text []byte) error {
type Gender string
type Locale language.Tag
//{
// SetLocale(language.Tag)
// Get() language.Tag
//}
//
//func NewLocale(tag language.Tag) Locale {
// if tag.IsRoot() {
// return nil
// }
// return &locale{Tag: tag}
//}
//
//type locale struct {
// language.Tag
//}
//
//func (l *locale) SetLocale(tag language.Tag) {
// l.Tag = tag
//}
//func (l *locale) Get() language.Tag {
// return l.Tag
//}
//func (l *locale) MarshalJSON() ([]byte, error) {
// if l != nil && !l.IsRoot() {
// return l.MarshalText()
// }
// return []byte("null"), nil
//}
type Locales []language.Tag
func (l *Locales) UnmarshalText(text []byte) error {
@ -92,11 +60,19 @@ type ResponseType string
type Scopes []string
func (s *Scopes) Encode() string {
return strings.Join(*s, " ")
}
func (s *Scopes) UnmarshalText(text []byte) error {
*s = strings.Split(string(text), " ")
return nil
}
func (s *Scopes) MarshalText() ([]byte, error) {
return []byte(s.Encode()), nil
}
type Time time.Time
func (t *Time) UnmarshalJSON(data []byte) error {

View file

@ -1,6 +1,7 @@
package oidc
import (
"bytes"
"encoding/json"
"testing"
@ -22,7 +23,15 @@ func TestAudience_UnmarshalText(t *testing.T) {
wantErr bool
}{
{
"unknown value",
"invalid value",
args{
[]byte(`{"aud": {"a": }}}`),
},
res{},
false,
},
{
"single audience",
args{
[]byte(`{"aud": "single audience"}`),
},
@ -32,7 +41,7 @@ func TestAudience_UnmarshalText(t *testing.T) {
false,
},
{
"page",
"multiple audience",
args{
[]byte(`{"aud": ["multiple", "audience"]}`),
},
@ -219,13 +228,12 @@ func TestScopes_UnmarshalText(t *testing.T) {
})
}
}
func TestTime_UnmarshalJSON(t *testing.T) {
func TestScopes_MarshalText(t *testing.T) {
type args struct {
text []byte
scopes Scopes
}
type res struct {
scopes []string
scopes []byte
}
tests := []struct {
name string
@ -236,41 +244,53 @@ func TestTime_UnmarshalJSON(t *testing.T) {
{
"unknown value",
args{
[]byte("unknown"),
Scopes{"unknown"},
},
res{
[]string{"unknown"},
[]byte("unknown"),
},
false,
},
{
"struct",
args{
Scopes{`{"unknown":"value"}`},
},
res{
[]byte(`{"unknown":"value"}`),
},
false,
},
{
"openid",
args{
[]byte("openid"),
Scopes{"openid"},
},
res{
[]string{"openid"},
[]byte("openid"),
},
false,
},
{
"multiple scopes",
args{
[]byte("openid email custom:scope"),
Scopes{"openid", "email", "custom:scope"},
},
res{
[]string{"openid", "email", "custom:scope"},
[]byte("openid email custom:scope"),
},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var scopes Scopes
if err := scopes.UnmarshalText(tt.args.text); (err != nil) != tt.wantErr {
t.Errorf("UnmarshalText() error = %v, wantErr %v", err, tt.wantErr)
text, err := tt.args.scopes.MarshalText()
if (err != nil) != tt.wantErr {
t.Errorf("MarshalText() error = %v, wantErr %v", err, tt.wantErr)
}
if !bytes.Equal(text, tt.res.scopes) {
t.Errorf("MarshalText() is = %q, want %q", text, tt.res.scopes)
}
assert.ElementsMatch(t, scopes, tt.res.scopes)
})
}
}