ContainerImage.Pinniped/internal/fositestorage/authorizationcode/authorizationcode.go

329 lines
9.5 KiB
Go
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package authorizationcode
import (
"context"
stderrors "errors"
"fmt"
"time"
"github.com/ory/fosite"
"github.com/ory/fosite/handler/oauth2"
"github.com/ory/fosite/handler/openid"
"k8s.io/apimachinery/pkg/api/errors"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
"go.pinniped.dev/internal/constable"
"go.pinniped.dev/internal/crud"
"go.pinniped.dev/internal/fositestorage"
)
const (
TypeLabelValue = "authcode"
ErrInvalidAuthorizeRequestData = constable.Error("authorization request data must be present")
ErrInvalidAuthorizeRequestVersion = constable.Error("authorization request data has wrong version")
authorizeCodeStorageVersion = "1"
)
var _ oauth2.AuthorizeCodeStorage = &authorizeCodeStorage{}
type authorizeCodeStorage struct {
storage crud.Storage
}
type AuthorizeCodeSession struct {
Active bool `json:"active"`
Request *fosite.Request `json:"request"`
Version string `json:"version"`
}
func New(secrets corev1client.SecretInterface, clock func() time.Time, sessionStorageLifetime time.Duration) oauth2.AuthorizeCodeStorage {
return &authorizeCodeStorage{storage: crud.New(TypeLabelValue, secrets, clock, sessionStorageLifetime)}
}
func (a *authorizeCodeStorage) CreateAuthorizeCodeSession(ctx context.Context, signature string, requester fosite.Requester) error {
// This conversion assumes that we do not wrap the default type in any way
// i.e. we use the default fosite.OAuth2Provider.NewAuthorizeRequest implementation
// note that because this type is serialized and stored in Kube, we cannot easily change the implementation later
request, err := fositestorage.ValidateAndExtractAuthorizeRequest(requester)
if err != nil {
return err
}
// Note, in case it is helpful, that Hydra stores specific fields from the requester:
// request ID
// requestedAt
// OAuth client ID
// requested scopes, granted scopes
// requested audience, granted audience
// url encoded request form
// session as JSON bytes with (optional) encryption
// session subject
// consent challenge from session which is the identifier ("authorization challenge")
// of the consent authorization request. It is used to identify the session.
// signature for lookup in the DB
_, err = a.storage.Create(ctx, signature, &AuthorizeCodeSession{Active: true, Request: request, Version: authorizeCodeStorageVersion}, nil)
return err
}
func (a *authorizeCodeStorage) GetAuthorizeCodeSession(ctx context.Context, signature string, _ fosite.Session) (fosite.Requester, error) {
// Note, in case it is helpful, that Hydra:
// - uses the incoming fosite.Session to provide the type needed to json.Unmarshal their session bytes
// - gets the client from its DB as a concrete type via client ID, the hydra memory client just validates that the
// client ID exists
// - hydra uses the sha512.Sum384 hash of signature when using JWT as access token to reduce length
session, _, err := a.getSession(ctx, signature)
// we need to always pass both the request and error back
if session == nil {
return nil, err
}
return session.Request, err
}
func (a *authorizeCodeStorage) InvalidateAuthorizeCodeSession(ctx context.Context, signature string) error {
session, rv, err := a.getSession(ctx, signature)
if err != nil {
return err
}
session.Active = false
if _, err := a.storage.Update(ctx, signature, rv, session); err != nil {
if errors.IsConflict(err) {
return &errSerializationFailureWithCause{cause: err}
}
return err
}
return nil
}
func (a *authorizeCodeStorage) getSession(ctx context.Context, signature string) (*AuthorizeCodeSession, string, error) {
session := NewValidEmptyAuthorizeCodeSession()
rv, err := a.storage.Get(ctx, signature, session)
if errors.IsNotFound(err) {
return nil, "", fosite.ErrNotFound.WithCause(err).WithDebug(err.Error())
}
if err != nil {
return nil, "", fmt.Errorf("failed to get authorization code session for %s: %w", signature, err)
}
if version := session.Version; version != authorizeCodeStorageVersion {
return nil, "", fmt.Errorf("%w: authorization code session for %s has version %s instead of %s",
ErrInvalidAuthorizeRequestVersion, signature, version, authorizeCodeStorageVersion)
}
if session.Request.ID == "" {
return nil, "", fmt.Errorf("malformed authorization code session for %s: %w", signature, ErrInvalidAuthorizeRequestData)
}
// we must return the session in this case to allow fosite to revoke the associated tokens
if !session.Active {
return session, rv, fmt.Errorf("authorization code session for %s has already been used: %w", signature, fosite.ErrInvalidatedAuthorizeCode)
}
return session, rv, nil
}
func NewValidEmptyAuthorizeCodeSession() *AuthorizeCodeSession {
return &AuthorizeCodeSession{
Request: &fosite.Request{
Client: &fosite.DefaultOpenIDConnectClient{},
Session: &openid.DefaultSession{},
},
}
}
var _ interface {
Is(error) bool
Unwrap() error
error
} = &errSerializationFailureWithCause{}
type errSerializationFailureWithCause struct {
cause error
}
func (e *errSerializationFailureWithCause) Is(err error) bool {
return stderrors.Is(fosite.ErrSerializationFailure, err)
}
func (e *errSerializationFailureWithCause) Unwrap() error {
return e.cause
}
func (e *errSerializationFailureWithCause) Error() string {
return fmt.Sprintf("%s: %s", fosite.ErrSerializationFailure, e.cause)
}
// ExpectedAuthorizeCodeSessionJSONFromFuzzing is used for round tripping tests.
// It is exported to allow integration tests to use it.
const ExpectedAuthorizeCodeSessionJSONFromFuzzing = `{
"active": true,
"request": {
"id": "嫎l蟲aƖ啘艿",
"requestedAt": "2082-11-10T18:36:11.627253638Z",
"client": {
"id": "!ſɄĈp[述齛ʘUȻ.5ȿE",
"client_secret": "UQ==",
"redirect_uris": [
"ǣ珑 ʑ飶畛Ȳ螤Yɫüeɯ紤邥翔勋\\",
"Bʒ;",
"鿃攴Ųęʍ鎾ʦ©cÏN,Ġ/_"
],
"grant_types": [
"憉sHĒ尥窘挼Ŀʼn"
],
"response_types": [
"4",
"ʄÔ@}i{絧遗Ū^ȝĸ谋Vʋ鱴閇T"
],
"scopes": [
"R鴝順諲ŮŚ节ȭŀȋc剠鏯ɽÿ¸"
],
"audience": [
"Ƥ"
],
"public": true,
"jwks_uri": "BA瘪囷ɫCʄɢ雐譄uée'",
"jwks": {
"keys": [
{
"kty": "OKP",
"crv": "Ed25519",
"x": "nK9xgX_iN7u3u_i8YOO7ZRT_WK028Vd_nhtsUu7Eo6E",
"x5u": {
"Scheme": "",
"Opaque": "",
"User": null,
"Host": "",
"Path": "",
"RawPath": "",
"ForceQuery": false,
"RawQuery": "",
"Fragment": "",
"RawFragment": ""
}
},
{
"kty": "OKP",
"crv": "Ed25519",
"x": "UbbswQgzWhfGCRlwQmMp6fw_HoIoqkIaKT-2XN2fuYU",
"x5u": {
"Scheme": "",
"Opaque": "",
"User": null,
"Host": "",
"Path": "",
"RawPath": "",
"ForceQuery": false,
"RawQuery": "",
"Fragment": "",
"RawFragment": ""
}
}
]
},
"token_endpoint_auth_method": "ŚǗƳȕ暭Q0ņP羾,塐",
"request_uris": [
"lj翻LH^俤µDzɹ@©|\u003eɃ",
"[:c顎疻紵D"
],
"request_object_signing_alg": "m1Ì恣S@T嵇LJV,Æ櫔袆鋹奘",
"token_endpoint_auth_signing_alg": "Fãƻʚ肈ą8O+a駣"
},
"scopes": [
"ɼk瘸'鴵yſǮŁ±\u003eFA曎餄FxD溪",
"綻N镪p赌h%桙dĽ"
],
"grantedScopes": [
"癗E]Ņʘʟ車s"
],
"form": {
"蹬器ķ8ŷ萒寎廭#疶昄Ą-Ƃƞ轵": [
"熞ĝƌĆ1ȇyǴ濎=Tʉȼʁŀ\u003c",
"耡q戨稞R÷mȵg釽[ƞ@",
"đ[嬧鱒Ȁ彆媚杨嶒ĤGÀ吧Lŷ"
],
"餟": [
"蒍z\u0026(K鵢Kj ŏ9Q韉Ķ%",
"輫ǘ(¨Ƞ亱6ě#嫀^xz ",
"@耢ɝ^¡!犃ĹĐJí¿ō擫"
]
},
"session": {
"Claims": {
"JTI": "懫砰¿C筽娴ƓaPu镈賆ŗɰ",
"Issuer": "皶竇瞍涘¹焕iǢǽɽĺŧ",
"Subject": "矠M6ɡǜg炾ʙ$%o6肿Ȫ",
"Audience": [
"ƌÙ鯆GQơ鮫R嫁ɍUƞ9+u!Ȱ踾$"
],
"Nonce": "us旸Ť/Õ薝隧;綡,鼞",
"ExpiresAt": "2065-11-30T13:47:03.613000626Z",
"IssuedAt": "1976-02-22T09:57:20.479850437Z",
"RequestedAt": "2016-04-13T04:18:53.648949323Z",
"AuthTime": "2098-07-12T04:38:54.034043015Z",
"AccessTokenHash": "滮]",
"AuthenticationContextClassReference": "°3\u003eÙ",
"AuthenticationMethodsReference": "k?µ鱔ǤÂ",
"CodeHash": "Țƒ1v¸KĶ跭};",
"Extra": {
"=ſ氆": {
"Ƿī,廖ʡ彑V\\廳蟕Ț": [
843216989
],
"蔯ʠ浵Ī": {
"H\"nǕ=rlƆ褡{ǏSȳŅ": {
"Žg": false
},
"枱鰧ɛ鸁A渇": null
}
},
"斻遟a衪荖舃9闄岈锘肺ńʥƕU}j%": 2520197933
}
},
"Headers": {
"Extra": {
"熒ɘȏıȒ諃龟ŴŠ'耐Ƭ扵ƹ玄ɕwL": {
"ýÏʥZq7烱藌\\捀¿őŧQ": {
"微'X焌襱ǭɕņ殥!_": null,
"荇届UȚ?戋璖$9\u00269舋": {
"ɕ餦ÑEǰ哤癨浦浏1Rk頓ć§蚲6": true
}
},
"鲒鿮禗O暒aJP鐜?ĮV嫎h譭ȉ]DĘ": [
954647573
]
},
"皩Ƭ}Ɇ.雬Ɨ´唁": 1572524915
}
},
"ExpiresAt": {
"\u003cqċ譈8ŪɎP绿MÅ": "2031-10-18T22:07:34.950803105Z",
"ȸěaʜD捛?½ʀ+Ċ偢镳ʬÍɷȓ\u003c": "2049-05-13T15:27:20.968432454Z"
},
"Username": "1藍殙菥趏酱Nʎ\u0026^横懋ƶ峦Fïȫƅw",
"Subject": "檾ĩĆ爨4犹|v炩f柏ʒ鴙*鸆偡"
},
"requestedAudience": [
"肯Ûx穞Ƀ",
"ź蕴3ǐ薝Ƅ腲=ʐ诂鱰屾Ê窢ɋ鄊qɠ谫"
],
"grantedAudience": [
"ǵƕ牀1鞊\\ȹ)}鉍商OɄƣ圔,xĪ",
"悾xn冏裻摼0Ʈ蚵Ȼ塕»£#稏扟X"
]
},
"version": "1"
}`