WIP towards using k8s fosite storage in the supervisor's callback endpoint

- Note that this WIP commit includes a failing unit test, which will
  be addressed in the next commit

Signed-off-by: Ryan Richard <richardry@vmware.com>
This commit is contained in:
Margo Crawford 2020-12-01 11:01:23 -08:00 committed by Ryan Richard
parent b272b3f331
commit c8eaa3f383
9 changed files with 548 additions and 260 deletions

View File

@ -62,17 +62,17 @@ func TestStorage(t *testing.T) {
}{ }{
{ {
name: "get non-existent", name: "get non-existent",
resource: "authorization-codes", resource: "authcode",
mocks: nil, mocks: nil,
run: func(t *testing.T, storage Storage) error { run: func(t *testing.T, storage Storage) error {
_, err := storage.Get(ctx, "not-exists", nil) _, err := storage.Get(ctx, "not-exists", nil)
return err return err
}, },
wantActions: []coretesting.Action{ wantActions: []coretesting.Action{
coretesting.NewGetAction(secretsGVR, namespace, "pinniped-storage-authorization-codes-t2fx46yyvs3a"), coretesting.NewGetAction(secretsGVR, namespace, "pinniped-storage-authcode-t2fx46yyvs3a"),
}, },
wantSecrets: nil, wantSecrets: nil,
wantErr: `failed to get authorization-codes for signature not-exists: secrets "pinniped-storage-authorization-codes-t2fx46yyvs3a" not found`, wantErr: `failed to get authcode for signature not-exists: secrets "pinniped-storage-authcode-t2fx46yyvs3a" not found`,
}, },
{ {
name: "delete non-existent", name: "delete non-existent",

View File

@ -19,7 +19,7 @@ import (
) )
const ( const (
ErrInvalidAuthorizeRequestType = constable.Error("authorization request must be of type fosite.AuthorizeRequest") ErrInvalidAuthorizeRequestType = constable.Error("authorization request must be of type fosite.Request")
ErrInvalidAuthorizeRequestData = constable.Error("authorization request data must not be nil") ErrInvalidAuthorizeRequestData = constable.Error("authorization request data must not be nil")
ErrInvalidAuthorizeRequestVersion = constable.Error("authorization request data has wrong version") ErrInvalidAuthorizeRequestVersion = constable.Error("authorization request data has wrong version")
@ -33,26 +33,25 @@ type authorizeCodeStorage struct {
} }
type AuthorizeCodeSession struct { type AuthorizeCodeSession struct {
Active bool `json:"active"` Active bool `json:"active"`
Request *fosite.AuthorizeRequest `json:"request"` Request *fosite.Request `json:"request"`
Version string `json:"version"` Version string `json:"version"`
} }
func New(secrets corev1client.SecretInterface) oauth2.AuthorizeCodeStorage { func New(secrets corev1client.SecretInterface) oauth2.AuthorizeCodeStorage {
return &authorizeCodeStorage{storage: crud.New("authorization-codes", secrets)} return &authorizeCodeStorage{storage: crud.New("authcode", secrets)}
} }
func (a *authorizeCodeStorage) CreateAuthorizeCodeSession(ctx context.Context, signature string, requester fosite.Requester) error { 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 // This conversion assumes that we do not wrap the default type in any way
// i.e. we use the default fosite.OAuth2Provider.NewAuthorizeRequest implementation // 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 // note that because this type is serialized and stored in Kube, we cannot easily change the implementation later
// TODO hydra uses the fosite.Request struct and ignores the extra fields in fosite.AuthorizeRequest
request, err := validateAndExtractAuthorizeRequest(requester) request, err := validateAndExtractAuthorizeRequest(requester)
if err != nil { if err != nil {
return err return err
} }
// TODO hydra stores specific fields from the requester // Note, in case it is helpful, that Hydra stores specific fields from the requester:
// request ID // request ID
// requestedAt // requestedAt
// OAuth client ID // OAuth client ID
@ -70,12 +69,11 @@ func (a *authorizeCodeStorage) CreateAuthorizeCodeSession(ctx context.Context, s
} }
func (a *authorizeCodeStorage) GetAuthorizeCodeSession(ctx context.Context, signature string, _ fosite.Session) (fosite.Requester, error) { func (a *authorizeCodeStorage) GetAuthorizeCodeSession(ctx context.Context, signature string, _ fosite.Session) (fosite.Requester, error) {
// TODO hydra uses the incoming fosite.Session to provide the type needed to json.Unmarshal their session bytes // Note, in case it is helpful, that Hydra:
// - uses the incoming fosite.Session to provide the type needed to json.Unmarshal their session bytes
// TODO hydra gets the client from its DB as a concrete type via client ID, // - gets the client from its DB as a concrete type via client ID, the hydra memory client just validates that the
// the hydra memory client just validates that the client ID exists // client ID exists
// - hydra uses the sha512.Sum384 hash of signature when using JWT as access token to reduce length
// TODO hydra uses the sha512.Sum384 hash of signature when using JWT as access token to reduce length
session, _, err := a.getSession(ctx, signature) session, _, err := a.getSession(ctx, signature)
@ -88,8 +86,6 @@ func (a *authorizeCodeStorage) GetAuthorizeCodeSession(ctx context.Context, sign
} }
func (a *authorizeCodeStorage) InvalidateAuthorizeCodeSession(ctx context.Context, signature string) error { func (a *authorizeCodeStorage) InvalidateAuthorizeCodeSession(ctx context.Context, signature string) error {
// TODO write garbage collector for these codes
session, rv, err := a.getSession(ctx, signature) session, rv, err := a.getSession(ctx, signature)
if err != nil { if err != nil {
return err return err
@ -137,17 +133,15 @@ func (a *authorizeCodeStorage) getSession(ctx context.Context, signature string)
func NewValidEmptyAuthorizeCodeSession() *AuthorizeCodeSession { func NewValidEmptyAuthorizeCodeSession() *AuthorizeCodeSession {
return &AuthorizeCodeSession{ return &AuthorizeCodeSession{
Request: &fosite.AuthorizeRequest{ Request: &fosite.Request{
Request: fosite.Request{ Client: &fosite.DefaultOpenIDConnectClient{},
Client: &fosite.DefaultOpenIDConnectClient{}, Session: &openid.DefaultSession{},
Session: &openid.DefaultSession{},
},
}, },
} }
} }
func validateAndExtractAuthorizeRequest(requester fosite.Requester) (*fosite.AuthorizeRequest, error) { func validateAndExtractAuthorizeRequest(requester fosite.Requester) (*fosite.Request, error) {
request, ok1 := requester.(*fosite.AuthorizeRequest) request, ok1 := requester.(*fosite.Request)
if !ok1 { if !ok1 {
return nil, ErrInvalidAuthorizeRequestType return nil, ErrInvalidAuthorizeRequestType
} }
@ -189,59 +183,37 @@ func (e *errSerializationFailureWithCause) Error() string {
const ExpectedAuthorizeCodeSessionJSONFromFuzzing = `{ const ExpectedAuthorizeCodeSessionJSONFromFuzzing = `{
"active": true, "active": true,
"request": { "request": {
"responseTypes": [ "id": "嫎l蟲aƖ啘艿",
"¥Îʒ襧.ɕ7崛瀇莒AȒ[ɠ牐7#$ɭ", "requestedAt": "2082-11-10T18:36:11.627253638Z",
".5ȿELj9ûF済(D疻翋膗",
"螤Yɫüeɯ紤邥翔勋\\RBʒ;-"
],
"redirectUri": {
"Scheme": "ħesƻU赒M喦_ģ",
"Opaque": "Ġ/_章Ņ缘T蝟NJ儱礹燃ɢ",
"User": {},
"Host": "ȳ4螘Wo",
"Path": "}i{",
"RawPath": "5Dža丝eF0eė鱊hǒx蔼Q",
"ForceQuery": true,
"RawQuery": "熤1bbWV",
"Fragment": "ȋc剠鏯ɽÿ¸",
"RawFragment": "qƤ"
},
"state": "@n,x竘Şǥ嗾稀'ã击漰怼禝穞梠Ǫs",
"handledResponseTypes": [
"m\"e尚鬞ƻɼ抹d誉y鿜Ķ"
],
"id": "ō澩ć|3U2Ǜl霨ǦǵpƉ",
"requestedAt": "1989-11-05T22:02:31.105295894Z",
"client": { "client": {
"id": "[:c顎疻紵D", "id": "!ſɄĈp[述齛ʘUȻ.5ȿE",
"client_secret": "mQ==", "client_secret": "UQ==",
"redirect_uris": [ "redirect_uris": [
"恣S@T嵇LJV,Æ櫔袆鋹奘菲", "ǣ珑 ʑ飶畛Ȳ螤Yɫüeɯ紤邥翔勋\\",
"ãƻʚ肈ą8O+a駣Ʉɼk瘸'鴵y" "Bʒ;",
"鿃攴Ųęʍ鎾ʦ©cÏN,Ġ/_"
], ],
"grant_types": [ "grant_types": [
".湆ê\"唐", "憉sHĒ尥窘挼Ŀʼn"
"曎餄FxD溪躲珫ÈşɜȨû臓嬣\"ǃŤz"
], ],
"response_types": [ "response_types": [
"Ņʘʟ車sʊ儓JǐŪɺǣy|耑ʄ" "4",
"ʄÔ@}i{絧遗Ū^ȝĸ谋Vʋ鱴閇T"
], ],
"scopes": [ "scopes": [
"Ą", "R鴝順諲ŮŚ节ȭŀȋc剠鏯ɽÿ¸"
"萙Į(潶饏熞ĝƌĆ1",
"əȤ4Į筦p煖鵄$睱奐耡q"
], ],
"audience": [ "audience": [
"Ʃǣ鿫/Ò敫ƤV" "Ƥ"
], ],
"public": true, "public": true,
"jwks_uri": "ȩđ[嬧鱒Ȁ彆媚杨嶒ĤG", "jwks_uri": "BA瘪囷ɫCʄɢ雐譄uée'",
"jwks": { "jwks": {
"keys": [ "keys": [
{ {
"kty": "OKP", "kty": "OKP",
"crv": "Ed25519", "crv": "Ed25519",
"x": "JmA-6KpjzqKu0lq9OiB6ORL4s2UzBFPsE1hm6vESeXM", "x": "nK9xgX_iN7u3u_i8YOO7ZRT_WK028Vd_nhtsUu7Eo6E",
"x5u": { "x5u": {
"Scheme": "", "Scheme": "",
"Opaque": "", "Opaque": "",
@ -258,24 +230,7 @@ const ExpectedAuthorizeCodeSessionJSONFromFuzzing = `{
{ {
"kty": "OKP", "kty": "OKP",
"crv": "Ed25519", "crv": "Ed25519",
"x": "LbRC1_3HEe5o7Japk9jFp3_7Ou7Gi2gpqrVrIi0eLDQ", "x": "UbbswQgzWhfGCRlwQmMp6fw_HoIoqkIaKT-2XN2fuYU",
"x5u": {
"Scheme": "",
"Opaque": "",
"User": null,
"Host": "",
"Path": "",
"RawPath": "",
"ForceQuery": false,
"RawQuery": "",
"Fragment": "",
"RawFragment": ""
}
},
{
"kty": "OKP",
"crv": "Ed25519",
"x": "Ovk4DF8Yn3mkULuTqnlGJxFnKGu9EL6Xcf2Nql9lK3c",
"x5u": { "x5u": {
"Scheme": "", "Scheme": "",
"Opaque": "", "Opaque": "",
@ -291,91 +246,95 @@ const ExpectedAuthorizeCodeSessionJSONFromFuzzing = `{
} }
] ]
}, },
"token_endpoint_auth_method": "\u0026(K鵢Kj ŏ9Q韉Ķ%嶑輫ǘ(", "token_endpoint_auth_method": "ŚǗƳȕ暭Q0ņP羾,塐",
"request_uris": [ "request_uris": [
":", "lj翻LH^俤µDzɹ@©|\u003eɃ",
"6ě#嫀^xz Ū胧r" "[:c顎疻紵D"
], ],
"request_object_signing_alg": "^¡!犃ĹĐJí¿ō擫ų懫砰¿", "request_object_signing_alg": "m1Ì恣S@T嵇LJV,Æ櫔袆鋹奘",
"token_endpoint_auth_signing_alg": "ƈŮå" "token_endpoint_auth_signing_alg": "Fãƻʚ肈ą8O+a駣"
}, },
"scopes": [ "scopes": [
"阃.Ù頀ʌGa皶竇瞍涘¹", "ɼk瘸'鴵yſǮŁ±\u003eFA曎餄FxD溪",
"ȽŮ切衖庀ŰŒ矠", "綻N镪p赌h%桙dĽ"
"楓)馻řĝǕ菸Tĕ1伞柲\u003c\"ʗȆ\\雤"
], ],
"grantedScopes": [ "grantedScopes": [
"ơ鮫R嫁ɍUƞ9+u!Ȱ", "癗E]Ņʘʟ車s"
"}Ă岜"
], ],
"form": { "form": {
"旸Ť/Õ薝隧;綡,鼞纂=": [ "蹬器ķ8ŷ萒寎廭#疶昄Ą-Ƃƞ轵": [
"[滮]憀", "熞ĝƌĆ1ȇyǴ濎=Tʉȼʁŀ\u003c",
"3\u003eÙœ蓄UK嗤眇疟Țƒ1v¸KĶ" "耡q戨稞R÷mȵg釽[ƞ@",
"đ[嬧鱒Ȁ彆媚杨嶒ĤGÀ吧Lŷ"
],
"餟": [
"蒍z\u0026(K鵢Kj ŏ9Q韉Ķ%",
"輫ǘ(¨Ƞ亱6ě#嫀^xz ",
"@耢ɝ^¡!犃ĹĐJí¿ō擫"
] ]
}, },
"session": { "session": {
"Claims": { "Claims": {
"JTI": "};Ų斻遟a衪荖舃", "JTI": "懫砰¿C筽娴ƓaPu镈賆ŗɰ",
"Issuer": "芠顋敀拲h蝺$!", "Issuer": "皶竇瞍涘¹焕iǢǽɽĺŧ",
"Subject": "}j%(=ſ氆]垲莲顇", "Subject": "矠M6ɡǜg炾ʙ$%o6肿Ȫ",
"Audience": [ "Audience": [
"彑V\\廳蟕Țǡ蔯ʠ浵Ī龉磈螖畭5", "ƌÙ鯆GQơ鮫R嫁ɍUƞ9+u!Ȱ踾$"
"渇Ȯʕc"
], ],
"Nonce": "Ǖ=rlƆ褡{ǏS", "Nonce": "us旸Ť/Õ薝隧;綡,鼞",
"ExpiresAt": "1975-11-17T14:21:34.205609651Z", "ExpiresAt": "2065-11-30T13:47:03.613000626Z",
"IssuedAt": "2104-07-03T15:40:03.66710966Z", "IssuedAt": "1976-02-22T09:57:20.479850437Z",
"RequestedAt": "2031-05-18T05:14:19.449350555Z", "RequestedAt": "2016-04-13T04:18:53.648949323Z",
"AuthTime": "2018-01-27T07:55:06.056862114Z", "AuthTime": "2098-07-12T04:38:54.034043015Z",
"AccessTokenHash": "鹰肁躧", "AccessTokenHash": "滮]",
"AuthenticationContextClassReference": "", "AuthenticationContextClassReference": "°3\u003eÙ",
"AuthenticationMethodsReference": "DQh:uȣ", "AuthenticationMethodsReference": "k?µ鱔ǤÂ",
"CodeHash": "ɘȏıȒ諃龟", "CodeHash": "Țƒ1v¸KĶ跭};",
"Extra": { "Extra": {
"a": { "=ſ氆": {
"^i臏f恡ƨ彮": { "Ƿī,廖ʡ彑V\\廳蟕Ț": [
"DĘ敨ýÏʥZq7烱藌\\": null, 843216989
"V": { ],
"őŧQĝ微'X焌襱ǭɕņ殥!_n": false "蔯ʠ浵Ī": {
} "H\"nǕ=rlƆ褡{ǏSȳŅ": {
}, "Žg": false
"Ż猁": [ },
1706822246 "枱鰧ɛ鸁A渇": null
] }
}, },
"Ò椪)ɫqň2搞Ŀ高摠鲒鿮禗O": 1233332227 "斻遟a衪荖舃9闄岈锘肺ńʥƕU}j%": 2520197933
} }
}, },
"Headers": { "Headers": {
"Extra": { "Extra": {
"?戋璖$9\u0026": { "熒ɘȏıȒ諃龟ŴŠ'耐Ƭ扵ƹ玄ɕwL": {
"µcɕ餦ÑEǰ哤癨浦浏1R": [ "ýÏʥZq7烱藌\\捀¿őŧQ": {
3761201123 "微'X焌襱ǭɕņ殥!_": null,
], "荇届UȚ?戋璖$9\u00269舋": {
"頓ć§蚲6rǦ\u003cqċ": { "ɕ餦ÑEǰ哤癨浦浏1Rk頓ć§蚲6": true
"Łʀ§ȏœɽDz斡冭ȸěaʜD捛?½ʀ+": null,
"ɒúIJ誠ƉyÖ.峷1藍殙菥趏": {
"jHȬȆ#)\u003cX": true
} }
} },
"鲒鿮禗O暒aJP鐜?ĮV嫎h譭ȉ]DĘ": [
954647573
]
}, },
"U": 1354158262 "皩Ƭ}Ɇ.雬Ɨ´唁": 1572524915
} }
}, },
"ExpiresAt": { "ExpiresAt": {
"\"嘬ȹĹaó剺撱Ȱ": "1985-09-09T04:35:40.533197189Z", "\u003cqċ譈8ŪɎP绿MÅ": "2031-10-18T22:07:34.950803105Z",
"ʆ\u003e": "1998-08-07T05:37:11.759718906Z", "ȸěaʜD捛?½ʀ+Ċ偢镳ʬÍɷȓ\u003c": "2049-05-13T15:27:20.968432454Z"
"柏ʒ鴙*鸆偡Ȓ肯Ûx": "2036-12-19T06:36:14.414805124Z"
}, },
"Username": "qmʎaðƠ绗ʢ緦Hū", "Username": "1藍殙菥趏酱Nʎ\u0026^横懋ƶ峦Fïȫƅw",
"Subject": "屾Ê窢ɋ鄊qɠ谫ǯǵƕ牀1鞊\\ȹ)" "Subject": "檾ĩĆ爨4犹|v炩f柏ʒ鴙*鸆偡"
}, },
"requestedAudience": [ "requestedAudience": [
"鉍商OɄƣ圔,xĪɏV鵅砍" "肯Ûx穞Ƀ",
"ź蕴3ǐ薝Ƅ腲=ʐ诂鱰屾Ê窢ɋ鄊qɠ谫"
], ],
"grantedAudience": [ "grantedAudience": [
"C笜嚯\u003cǐšɚĀĥʋ6鉅\\þc涎漄Ɨ腼" "ǵƕ牀1鞊\\ȹ)}鉍商OɄƣ圔,xĪ",
"悾xn冏裻摼0Ʈ蚵Ȼ塕»£#稏扟X"
] ]
}, },
"version": "1" "version": "1"

View File

@ -55,56 +55,39 @@ func TestAuthorizeCodeStorage(t *testing.T) {
name: "create, get, invalidate standard flow", name: "create, get, invalidate standard flow",
mocks: nil, mocks: nil,
run: func(t *testing.T, storage oauth2.AuthorizeCodeStorage) error { run: func(t *testing.T, storage oauth2.AuthorizeCodeStorage) error {
request := &fosite.AuthorizeRequest{ request := &fosite.Request{
ResponseTypes: fosite.Arguments{"not-code"}, ID: "abcd-1",
RedirectURI: &url.URL{ RequestedAt: time.Time{},
Scheme: "", Client: &fosite.DefaultOpenIDConnectClient{
Opaque: "weee", DefaultClient: &fosite.DefaultClient{
User: &url.Userinfo{}, ID: "pinny",
Host: "", Secret: nil,
Path: "/callback", RedirectURIs: nil,
RawPath: "", GrantTypes: nil,
ForceQuery: false, ResponseTypes: nil,
RawQuery: "", Scopes: nil,
Fragment: "", Audience: nil,
RawFragment: "", Public: true,
},
State: "stated",
HandledResponseTypes: fosite.Arguments{"not-type"},
Request: fosite.Request{
ID: "abcd-1",
RequestedAt: time.Time{},
Client: &fosite.DefaultOpenIDConnectClient{
DefaultClient: &fosite.DefaultClient{
ID: "pinny",
Secret: nil,
RedirectURIs: nil,
GrantTypes: nil,
ResponseTypes: nil,
Scopes: nil,
Audience: nil,
Public: true,
},
JSONWebKeysURI: "where",
JSONWebKeys: nil,
TokenEndpointAuthMethod: "something",
RequestURIs: nil,
RequestObjectSigningAlgorithm: "",
TokenEndpointAuthSigningAlgorithm: "",
}, },
RequestedScope: nil, JSONWebKeysURI: "where",
GrantedScope: nil, JSONWebKeys: nil,
Form: url.Values{"key": []string{"val"}}, TokenEndpointAuthMethod: "something",
Session: &openid.DefaultSession{ RequestURIs: nil,
Claims: nil, RequestObjectSigningAlgorithm: "",
Headers: nil, TokenEndpointAuthSigningAlgorithm: "",
ExpiresAt: nil,
Username: "snorlax",
Subject: "panda",
},
RequestedAudience: nil,
GrantedAudience: nil,
}, },
RequestedScope: nil,
GrantedScope: nil,
Form: url.Values{"key": []string{"val"}},
Session: &openid.DefaultSession{
Claims: nil,
Headers: nil,
ExpiresAt: nil,
Username: "snorlax",
Subject: "panda",
},
RequestedAudience: nil,
GrantedAudience: nil,
} }
err := storage.CreateAuthorizeCodeSession(ctx, "fancy-signature", request) err := storage.CreateAuthorizeCodeSession(ctx, "fancy-signature", request)
require.NoError(t, err) require.NoError(t, err)
@ -118,50 +101,50 @@ func TestAuthorizeCodeStorage(t *testing.T) {
wantActions: []coretesting.Action{ wantActions: []coretesting.Action{
coretesting.NewCreateAction(secretsGVR, namespace, &corev1.Secret{ coretesting.NewCreateAction(secretsGVR, namespace, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "pinniped-storage-authorization-codes-pwu5zs7lekbhnln2w4", Name: "pinniped-storage-authcode-pwu5zs7lekbhnln2w4",
ResourceVersion: "", ResourceVersion: "",
Labels: map[string]string{ Labels: map[string]string{
"storage.pinniped.dev": "authorization-codes", "storage.pinniped.dev": "authcode",
}, },
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"pinniped-storage-data": []byte(`{"active":true,"request":{"responseTypes":["not-code"],"redirectUri":{"Scheme":"","Opaque":"weee","User":{},"Host":"","Path":"/callback","RawPath":"","ForceQuery":false,"RawQuery":"","Fragment":"","RawFragment":""},"state":"stated","handledResponseTypes":["not-type"],"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"requestedAudience":null,"grantedAudience":null},"version":"1"}`), "pinniped-storage-data": []byte(`{"active":true,"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"requestedAudience":null,"grantedAudience":null},"version":"1"}`),
"pinniped-storage-version": []byte("1"), "pinniped-storage-version": []byte("1"),
}, },
Type: "storage.pinniped.dev/authorization-codes", Type: "storage.pinniped.dev/authcode",
}), }),
coretesting.NewGetAction(secretsGVR, namespace, "pinniped-storage-authorization-codes-pwu5zs7lekbhnln2w4"), coretesting.NewGetAction(secretsGVR, namespace, "pinniped-storage-authcode-pwu5zs7lekbhnln2w4"),
coretesting.NewGetAction(secretsGVR, namespace, "pinniped-storage-authorization-codes-pwu5zs7lekbhnln2w4"), coretesting.NewGetAction(secretsGVR, namespace, "pinniped-storage-authcode-pwu5zs7lekbhnln2w4"),
coretesting.NewUpdateAction(secretsGVR, namespace, &corev1.Secret{ coretesting.NewUpdateAction(secretsGVR, namespace, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "pinniped-storage-authorization-codes-pwu5zs7lekbhnln2w4", Name: "pinniped-storage-authcode-pwu5zs7lekbhnln2w4",
ResourceVersion: "", ResourceVersion: "",
Labels: map[string]string{ Labels: map[string]string{
"storage.pinniped.dev": "authorization-codes", "storage.pinniped.dev": "authcode",
}, },
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"pinniped-storage-data": []byte(`{"active":false,"request":{"responseTypes":["not-code"],"redirectUri":{"Scheme":"","Opaque":"weee","User":{},"Host":"","Path":"/callback","RawPath":"","ForceQuery":false,"RawQuery":"","Fragment":"","RawFragment":""},"state":"stated","handledResponseTypes":["not-type"],"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"requestedAudience":null,"grantedAudience":null},"version":"1"}`), "pinniped-storage-data": []byte(`{"active":false,"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"requestedAudience":null,"grantedAudience":null},"version":"1"}`),
"pinniped-storage-version": []byte("1"), "pinniped-storage-version": []byte("1"),
}, },
Type: "storage.pinniped.dev/authorization-codes", Type: "storage.pinniped.dev/authcode",
}), }),
}, },
wantSecrets: []corev1.Secret{ wantSecrets: []corev1.Secret{
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "pinniped-storage-authorization-codes-pwu5zs7lekbhnln2w4", Name: "pinniped-storage-authcode-pwu5zs7lekbhnln2w4",
Namespace: namespace, Namespace: namespace,
ResourceVersion: "", ResourceVersion: "",
Labels: map[string]string{ Labels: map[string]string{
"storage.pinniped.dev": "authorization-codes", "storage.pinniped.dev": "authcode",
}, },
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"pinniped-storage-data": []byte(`{"active":false,"request":{"responseTypes":["not-code"],"redirectUri":{"Scheme":"","Opaque":"weee","User":{},"Host":"","Path":"/callback","RawPath":"","ForceQuery":false,"RawQuery":"","Fragment":"","RawFragment":""},"state":"stated","handledResponseTypes":["not-type"],"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"requestedAudience":null,"grantedAudience":null},"version":"1"}`), "pinniped-storage-data": []byte(`{"active":false,"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"requestedAudience":null,"grantedAudience":null},"version":"1"}`),
"pinniped-storage-version": []byte("1"), "pinniped-storage-version": []byte("1"),
}, },
Type: "storage.pinniped.dev/authorization-codes", Type: "storage.pinniped.dev/authcode",
}, },
}, },
wantErr: "", wantErr: "",
@ -210,8 +193,8 @@ func TestFuzzAndJSONNewValidEmptyAuthorizeCodeSession(t *testing.T) {
require.Equal(t, validSession.Request, extractedRequest) require.Equal(t, validSession.Request, extractedRequest)
// checked above // checked above
defaultClient := validSession.Request.Request.Client.(*fosite.DefaultOpenIDConnectClient) defaultClient := validSession.Request.Client.(*fosite.DefaultOpenIDConnectClient)
defaultSession := validSession.Request.Request.Session.(*openid.DefaultSession) defaultSession := validSession.Request.Session.(*openid.DefaultSession)
// makes it easier to use a raw string // makes it easier to use a raw string
replacer := strings.NewReplacer("`", "a") replacer := strings.NewReplacer("`", "a")
@ -225,10 +208,10 @@ func TestFuzzAndJSONNewValidEmptyAuthorizeCodeSession(t *testing.T) {
} }
} }
// deterministic fuzzing of fosite.AuthorizeRequest // deterministic fuzzing of fosite.Request
f := fuzz.New().RandSource(rand.NewSource(1)).NilChance(0).NumElements(1, 3).Funcs( f := fuzz.New().RandSource(rand.NewSource(1)).NilChance(0).NumElements(1, 3).Funcs(
// these functions guarantee that these are the only interface types we need to fill out // these functions guarantee that these are the only interface types we need to fill out
// if fosite.AuthorizeRequest changes to add more, the fuzzer will panic // if fosite.Request changes to add more, the fuzzer will panic
func(fc *fosite.Client, c fuzz.Continue) { func(fc *fosite.Client, c fuzz.Continue) {
c.Fuzz(defaultClient) c.Fuzz(defaultClient)
*fc = defaultClient *fc = defaultClient

View File

@ -0,0 +1,115 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package pkce
import (
"context"
"fmt"
"github.com/ory/fosite"
"github.com/ory/fosite/handler/openid"
"github.com/ory/fosite/handler/pkce"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
"go.pinniped.dev/internal/constable"
"go.pinniped.dev/internal/crud"
)
const (
ErrInvalidPKCERequestType = constable.Error("requester must be of type fosite.Request")
pkceStorageVersion = "1"
)
var _ pkce.PKCERequestStorage = &pkceStorage{}
type pkceStorage struct {
storage crud.Storage
}
type session struct {
Request *fosite.Request `json:"request"`
Version string `json:"version"`
}
func New(secrets corev1client.SecretInterface) pkce.PKCERequestStorage {
return &pkceStorage{storage: crud.New("pkce", secrets)}
}
// TODO test what happens when we pass nil as the requester.
func (a *pkceStorage) CreatePKCERequestSession(ctx context.Context, signature string, requester fosite.Requester) error {
request, err := validateAndExtractAuthorizeRequest(requester)
if err != nil {
return err
}
_, err = a.storage.Create(ctx, signature, &session{Request: request, Version: pkceStorageVersion})
return err
}
func (a *pkceStorage) GetPKCERequestSession(ctx context.Context, signature string, _ fosite.Session) (fosite.Requester, error) {
session, _, err := a.getSession(ctx, signature)
if err != nil {
return nil, err
}
return session.Request, err
}
func (a *pkceStorage) DeletePKCERequestSession(ctx context.Context, signature string) error {
return a.storage.Delete(ctx, signature)
}
func (a *pkceStorage) getSession(ctx context.Context, signature string) (*session, string, error) {
session := newValidEmptyPKCESession()
rv, err := a.storage.Get(ctx, signature, session)
// TODO we do want this
// 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)
}
// TODO we probably want this
// if version := session.Version; version != pkceStorageVersion {
// return nil, "", fmt.Errorf("%w: authorization code session for %s has version %s instead of %s",
// ErrInvalidAuthorizeRequestVersion, signature, version, pkceStorageVersion)
// }
// TODO maybe we want this. it would only apply when a human has edited the secret.
// if session.Request == nil {
// return nil, "", fmt.Errorf("malformed authorization code session for %s: %w", signature, ErrInvalidAuthorizeRequestData)
// }
return session, rv, nil
}
func newValidEmptyPKCESession() *session {
return &session{
Request: &fosite.Request{
Client: &fosite.DefaultOpenIDConnectClient{},
Session: &openid.DefaultSession{},
},
}
}
func validateAndExtractAuthorizeRequest(requester fosite.Requester) (*fosite.Request, error) {
request, ok1 := requester.(*fosite.Request)
if !ok1 {
return nil, ErrInvalidPKCERequestType
}
_, ok2 := request.Client.(*fosite.DefaultOpenIDConnectClient)
_, ok3 := request.Session.(*openid.DefaultSession)
valid := ok2 && ok3
if !valid {
return nil, ErrInvalidPKCERequestType
}
return request, nil
}

View File

@ -0,0 +1,100 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package pkce
import (
"context"
"net/url"
"testing"
"time"
"github.com/ory/fosite"
"github.com/ory/fosite/handler/openid"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes/fake"
coretesting "k8s.io/client-go/testing"
)
func TestPKCEStorage(t *testing.T) {
ctx := context.Background()
secretsGVR := schema.GroupVersionResource{
Group: "",
Version: "v1",
Resource: "secrets",
}
const namespace = "test-ns"
wantActions := []coretesting.Action{
coretesting.NewCreateAction(secretsGVR, namespace, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "pinniped-storage-pkce-pwu5zs7lekbhnln2w4",
ResourceVersion: "",
Labels: map[string]string{
"storage.pinniped.dev": "pkce",
},
},
Data: map[string][]byte{
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"requestedAudience":null,"grantedAudience":null},"version":"1"}`),
"pinniped-storage-version": []byte("1"),
},
Type: "storage.pinniped.dev/pkce",
}),
coretesting.NewGetAction(secretsGVR, namespace, "pinniped-storage-pkce-pwu5zs7lekbhnln2w4"),
coretesting.NewDeleteAction(secretsGVR, namespace, "pinniped-storage-pkce-pwu5zs7lekbhnln2w4"),
}
client := fake.NewSimpleClientset()
secrets := client.CoreV1().Secrets(namespace)
storage := New(secrets)
request := &fosite.Request{
ID: "abcd-1",
RequestedAt: time.Time{},
Client: &fosite.DefaultOpenIDConnectClient{
DefaultClient: &fosite.DefaultClient{
ID: "pinny",
Secret: nil,
RedirectURIs: nil,
GrantTypes: nil,
ResponseTypes: nil,
Scopes: nil,
Audience: nil,
Public: true,
},
JSONWebKeysURI: "where",
JSONWebKeys: nil,
TokenEndpointAuthMethod: "something",
RequestURIs: nil,
RequestObjectSigningAlgorithm: "",
TokenEndpointAuthSigningAlgorithm: "",
},
RequestedScope: nil,
GrantedScope: nil,
Form: url.Values{"key": []string{"val"}},
Session: &openid.DefaultSession{
Claims: nil,
Headers: nil,
ExpiresAt: nil,
Username: "snorlax",
Subject: "panda",
},
RequestedAudience: nil,
GrantedAudience: nil,
}
err := storage.CreatePKCERequestSession(ctx, "fancy-signature", request)
require.NoError(t, err)
newRequest, err := storage.GetPKCERequestSession(ctx, "fancy-signature", nil)
require.NoError(t, err)
require.Equal(t, request, newRequest)
err = storage.DeletePKCERequestSession(ctx, "fancy-signature")
require.NoError(t, err)
require.Equal(t, wantActions, client.Actions())
}

View File

@ -17,8 +17,11 @@ import (
"github.com/gorilla/securecookie" "github.com/gorilla/securecookie"
"github.com/ory/fosite" "github.com/ory/fosite"
"github.com/ory/fosite/handler/openid" "github.com/ory/fosite/handler/openid"
"github.com/ory/fosite/storage"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes/fake"
kubetesting "k8s.io/client-go/testing"
"go.pinniped.dev/internal/oidc" "go.pinniped.dev/internal/oidc"
"go.pinniped.dev/internal/oidc/oidctestutil" "go.pinniped.dev/internal/oidc/oidctestutil"
@ -419,14 +422,12 @@ func TestCallbackEndpoint(t *testing.T) {
test := test test := test
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
// Configure fosite the same way that the production code would, except use in-memory storage. client := fake.NewSimpleClientset()
secrets := client.CoreV1().Secrets("some-namespace")
// Configure fosite the same way that the production code would.
// Inject this into our test subject at the last second so we get a fresh storage for every test. // Inject this into our test subject at the last second so we get a fresh storage for every test.
oauthStore := &storage.MemoryStore{ oauthStore := oidc.NewKubeStorage(secrets)
Clients: map[string]fosite.Client{oidc.PinnipedCLIOIDCClient().ID: oidc.PinnipedCLIOIDCClient()},
AuthorizeCodes: map[string]storage.StoreAuthorizeCode{},
PKCES: map[string]fosite.Requester{},
IDSessions: map[string]fosite.Requester{},
}
hmacSecret := []byte("some secret - must have at least 32 bytes") hmacSecret := []byte("some secret - must have at least 32 bytes")
require.GreaterOrEqual(t, len(hmacSecret), 32, "fosite requires that hmac secrets have at least 32 bytes") require.GreaterOrEqual(t, len(hmacSecret), 32, "fosite requires that hmac secrets have at least 32 bytes")
oauthHelper := oidc.FositeOauth2Helper(oauthStore, downstreamIssuer, hmacSecret) oauthHelper := oidc.FositeOauth2Helper(oauthStore, downstreamIssuer, hmacSecret)
@ -471,6 +472,21 @@ func TestCallbackEndpoint(t *testing.T) {
authcodeDataAndSignature := strings.Split(capturedAuthCode, ".") authcodeDataAndSignature := strings.Split(capturedAuthCode, ".")
require.Len(t, authcodeDataAndSignature, 2) require.Len(t, authcodeDataAndSignature, 2)
// Several Secrets should have been created
expectedNumberOfCreatedSecrets := 2
if test.wantGrantedOpenidScope {
expectedNumberOfCreatedSecrets++
}
require.Len(t, client.Actions(), expectedNumberOfCreatedSecrets)
// One authcode should have been stored.
actualAction := client.Actions()[0].(kubetesting.CreateActionImpl)
require.Equal(t, "create", actualAction.GetVerb())
require.Equal(t, schema.GroupVersionResource{Group: "", Version: "v1", Resource: "secrets"}, actualAction.GetResource())
actualSecret := actualAction.GetObject().(*corev1.Secret)
require.True(t, strings.HasPrefix(actualSecret.Name, "pinniped-storage-authcode-"))
require.Empty(t, actualSecret.Namespace) // because the secrets client is already scoped to a namespace
storedRequestFromAuthcode, storedSessionFromAuthcode := validateAuthcodeStorage( storedRequestFromAuthcode, storedSessionFromAuthcode := validateAuthcodeStorage(
t, t,
oauthStore, oauthStore,
@ -481,6 +497,14 @@ func TestCallbackEndpoint(t *testing.T) {
test.wantDownstreamRequestedScopes, test.wantDownstreamRequestedScopes,
) )
// One PKCE should have been stored.
actualAction = client.Actions()[1].(kubetesting.CreateActionImpl)
require.Equal(t, "create", actualAction.GetVerb())
require.Equal(t, schema.GroupVersionResource{Group: "", Version: "v1", Resource: "secrets"}, actualAction.GetResource())
actualSecret = actualAction.GetObject().(*corev1.Secret)
require.True(t, strings.HasPrefix(actualSecret.Name, "pinniped-storage-pkce-"))
require.Empty(t, actualSecret.Namespace) // because the secrets client is already scoped to a namespace
validatePKCEStorage( validatePKCEStorage(
t, t,
oauthStore, oauthStore,
@ -491,15 +515,24 @@ func TestCallbackEndpoint(t *testing.T) {
test.wantDownstreamPKCEChallengeMethod, test.wantDownstreamPKCEChallengeMethod,
) )
validateIDSessionStorage( // One IDSession should have been stored, if the downstream actually requested the "openid" scope
t, if test.wantGrantedOpenidScope {
oauthStore, actualAction = client.Actions()[2].(kubetesting.CreateActionImpl)
capturedAuthCode, // IDSession store key is full authcode require.Equal(t, "create", actualAction.GetVerb())
storedRequestFromAuthcode, require.Equal(t, schema.GroupVersionResource{Group: "", Version: "v1", Resource: "secrets"}, actualAction.GetResource())
storedSessionFromAuthcode, actualSecret = actualAction.GetObject().(*corev1.Secret)
test.wantGrantedOpenidScope, require.True(t, strings.HasPrefix(actualSecret.Name, "pinniped-storage-idsession-"))
test.wantDownstreamNonce, require.Empty(t, actualSecret.Namespace) // because the secrets client is already scoped to a namespace
)
validateIDSessionStorage(
t,
oauthStore,
capturedAuthCode, // IDSession store key is full authcode
storedRequestFromAuthcode,
storedSessionFromAuthcode,
test.wantDownstreamNonce,
)
}
} }
}) })
} }
@ -674,7 +707,7 @@ func shallowCopyAndModifyQuery(query url.Values, modifications map[string]string
func validateAuthcodeStorage( func validateAuthcodeStorage(
t *testing.T, t *testing.T,
oauthStore *storage.MemoryStore, oauthStore *oidc.KubeStorage,
storeKey string, storeKey string,
wantGrantedOpenidScope bool, wantGrantedOpenidScope bool,
wantDownstreamIDTokenSubject string, wantDownstreamIDTokenSubject string,
@ -683,9 +716,6 @@ func validateAuthcodeStorage(
) (*fosite.Request, *openid.DefaultSession) { ) (*fosite.Request, *openid.DefaultSession) {
t.Helper() t.Helper()
// One authcode should have been stored.
require.Len(t, oauthStore.AuthorizeCodes, 1)
// Get the authcode session back from storage so we can require that it was stored correctly. // Get the authcode session back from storage so we can require that it was stored correctly.
storedAuthorizeRequestFromAuthcode, err := oauthStore.GetAuthorizeCodeSession(context.Background(), storeKey, nil) storedAuthorizeRequestFromAuthcode, err := oauthStore.GetAuthorizeCodeSession(context.Background(), storeKey, nil)
require.NoError(t, err) require.NoError(t, err)
@ -725,7 +755,7 @@ func validateAuthcodeStorage(
require.Equal(t, wantDownstreamIDTokenSubject, actualClaims.Subject) require.Equal(t, wantDownstreamIDTokenSubject, actualClaims.Subject)
if wantDownstreamIDTokenGroups != nil { if wantDownstreamIDTokenGroups != nil {
require.Len(t, actualClaims.Extra, 1) require.Len(t, actualClaims.Extra, 1)
require.Equal(t, wantDownstreamIDTokenGroups, actualClaims.Extra["groups"]) require.ElementsMatch(t, wantDownstreamIDTokenGroups, actualClaims.Extra["groups"])
} else { } else {
require.Empty(t, actualClaims.Extra) require.Empty(t, actualClaims.Extra)
require.NotContains(t, actualClaims.Extra, "groups") require.NotContains(t, actualClaims.Extra, "groups")
@ -760,7 +790,7 @@ func validateAuthcodeStorage(
func validatePKCEStorage( func validatePKCEStorage(
t *testing.T, t *testing.T,
oauthStore *storage.MemoryStore, oauthStore *oidc.KubeStorage,
storeKey string, storeKey string,
storedRequestFromAuthcode *fosite.Request, storedRequestFromAuthcode *fosite.Request,
storedSessionFromAuthcode *openid.DefaultSession, storedSessionFromAuthcode *openid.DefaultSession,
@ -768,8 +798,6 @@ func validatePKCEStorage(
) { ) {
t.Helper() t.Helper()
// One PKCE should have been stored.
require.Len(t, oauthStore.PKCES, 1)
storedAuthorizeRequestFromPKCE, err := oauthStore.GetPKCERequestSession(context.Background(), storeKey, nil) storedAuthorizeRequestFromPKCE, err := oauthStore.GetPKCERequestSession(context.Background(), storeKey, nil)
require.NoError(t, err) require.NoError(t, err)
@ -787,33 +815,26 @@ func validatePKCEStorage(
func validateIDSessionStorage( func validateIDSessionStorage(
t *testing.T, t *testing.T,
oauthStore *storage.MemoryStore, oauthStore *oidc.KubeStorage,
storeKey string, storeKey string,
storedRequestFromAuthcode *fosite.Request, storedRequestFromAuthcode *fosite.Request,
storedSessionFromAuthcode *openid.DefaultSession, storedSessionFromAuthcode *openid.DefaultSession,
wantGrantedOpenidScope bool,
wantDownstreamNonce string, wantDownstreamNonce string,
) { ) {
t.Helper() t.Helper()
// One IDSession should have been stored, if the downstream actually requested the "openid" scope.. storedAuthorizeRequestFromIDSession, err := oauthStore.GetOpenIDConnectSession(context.Background(), storeKey, nil)
if wantGrantedOpenidScope { require.NoError(t, err)
require.Len(t, oauthStore.IDSessions, 1)
storedAuthorizeRequestFromIDSession, err := oauthStore.GetOpenIDConnectSession(context.Background(), storeKey, nil)
require.NoError(t, err)
// Check that storage returned the expected concrete data types. // Check that storage returned the expected concrete data types.
storedRequestFromIDSession, storedSessionFromIDSession := castStoredAuthorizeRequest(t, storedAuthorizeRequestFromIDSession) storedRequestFromIDSession, storedSessionFromIDSession := castStoredAuthorizeRequest(t, storedAuthorizeRequestFromIDSession)
// The stored IDSession request should be the same as the stored authcode request. // The stored IDSession request should be the same as the stored authcode request.
require.Equal(t, storedRequestFromAuthcode.ID, storedRequestFromIDSession.ID) require.Equal(t, storedRequestFromAuthcode.ID, storedRequestFromIDSession.ID)
require.Equal(t, storedSessionFromAuthcode, storedSessionFromIDSession) require.Equal(t, storedSessionFromAuthcode, storedSessionFromIDSession)
// The stored IDSession request should also contain the nonce that the downstream sent us. // The stored IDSession request should also contain the nonce that the downstream sent us.
require.Equal(t, wantDownstreamNonce, storedRequestFromIDSession.Form.Get("nonce")) require.Equal(t, wantDownstreamNonce, storedRequestFromIDSession.Form.Get("nonce"))
} else {
require.Len(t, oauthStore.IDSessions, 0)
}
} }
func castStoredAuthorizeRequest(t *testing.T, storedAuthorizeRequest fosite.Requester) (*fosite.Request, *openid.DefaultSession) { func castStoredAuthorizeRequest(t *testing.T, storedAuthorizeRequest fosite.Requester) (*fosite.Request, *openid.DefaultSession) {

View File

@ -0,0 +1,110 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package oidc
import (
"context"
"time"
"github.com/ory/fosite"
"github.com/ory/fosite/handler/oauth2"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
"go.pinniped.dev/internal/constable"
"go.pinniped.dev/internal/fositestorage/authorizationcode"
)
const errKubeStorageNotImplemented = constable.Error("KubeStorage does not implement this method. It should not have been called.")
type KubeStorage struct {
authorizationCodeStorage oauth2.AuthorizeCodeStorage
}
func NewKubeStorage(secrets corev1client.SecretInterface) *KubeStorage {
return &KubeStorage{authorizationCodeStorage: authorizationcode.New(secrets)}
}
func (KubeStorage) RevokeRefreshToken(_ context.Context, _ string) error {
return errKubeStorageNotImplemented
}
func (KubeStorage) RevokeAccessToken(_ context.Context, _ string) error {
return errKubeStorageNotImplemented
}
func (KubeStorage) CreateRefreshTokenSession(_ context.Context, _ string, _ fosite.Requester) (err error) {
return nil
}
func (KubeStorage) GetRefreshTokenSession(_ context.Context, _ string, _ fosite.Session) (request fosite.Requester, err error) {
return nil, errKubeStorageNotImplemented
}
func (KubeStorage) DeleteRefreshTokenSession(_ context.Context, _ string) (err error) {
return errKubeStorageNotImplemented
}
func (KubeStorage) CreateAccessTokenSession(_ context.Context, _ string, _ fosite.Requester) (err error) {
return nil
}
func (KubeStorage) GetAccessTokenSession(_ context.Context, _ string, _ fosite.Session) (request fosite.Requester, err error) {
return nil, errKubeStorageNotImplemented
}
func (KubeStorage) DeleteAccessTokenSession(_ context.Context, _ string) (err error) {
return errKubeStorageNotImplemented
}
func (KubeStorage) CreateOpenIDConnectSession(_ context.Context, _ string, _ fosite.Requester) error {
return nil
}
func (KubeStorage) GetOpenIDConnectSession(_ context.Context, _ string, _ fosite.Requester) (fosite.Requester, error) {
return nil, errKubeStorageNotImplemented
}
func (KubeStorage) DeleteOpenIDConnectSession(_ context.Context, _ string) error {
return errKubeStorageNotImplemented
}
func (KubeStorage) GetPKCERequestSession(_ context.Context, _ string, _ fosite.Session) (fosite.Requester, error) {
return nil, errKubeStorageNotImplemented
}
func (KubeStorage) CreatePKCERequestSession(_ context.Context, _ string, _ fosite.Requester) error {
return nil
}
func (KubeStorage) DeletePKCERequestSession(_ context.Context, _ string) error {
return errKubeStorageNotImplemented
}
func (k KubeStorage) CreateAuthorizeCodeSession(ctx context.Context, signature string, r fosite.Requester) (err error) {
return k.authorizationCodeStorage.CreateAuthorizeCodeSession(ctx, signature, r)
}
func (k KubeStorage) GetAuthorizeCodeSession(ctx context.Context, signature string, s fosite.Session) (request fosite.Requester, err error) {
return k.authorizationCodeStorage.GetAuthorizeCodeSession(ctx, signature, s)
}
func (k KubeStorage) InvalidateAuthorizeCodeSession(ctx context.Context, signature string) (err error) {
return k.authorizationCodeStorage.InvalidateAuthorizeCodeSession(ctx, signature)
}
func (KubeStorage) GetClient(_ context.Context, id string) (fosite.Client, error) {
client := PinnipedCLIOIDCClient()
if client.ID == id {
return client, nil
}
return nil, fosite.ErrNotFound
}
func (KubeStorage) ClientAssertionJWTValid(_ context.Context, _ string) error {
return errKubeStorageNotImplemented
}
func (KubeStorage) SetClientAssertionJWT(_ context.Context, _ string, _ time.Time) error {
return errKubeStorageNotImplemented
}

View File

@ -12,16 +12,16 @@ import (
"go.pinniped.dev/internal/constable" "go.pinniped.dev/internal/constable"
) )
const errNotImplemented = constable.Error("NullStorage does not implement this method. It should not have been called.") const errNullStorageNotImplemented = constable.Error("NullStorage does not implement this method. It should not have been called.")
type NullStorage struct{} type NullStorage struct{}
func (NullStorage) RevokeRefreshToken(_ context.Context, _ string) error { func (NullStorage) RevokeRefreshToken(_ context.Context, _ string) error {
return errNotImplemented return errNullStorageNotImplemented
} }
func (NullStorage) RevokeAccessToken(_ context.Context, _ string) error { func (NullStorage) RevokeAccessToken(_ context.Context, _ string) error {
return errNotImplemented return errNullStorageNotImplemented
} }
func (NullStorage) CreateRefreshTokenSession(_ context.Context, _ string, _ fosite.Requester) (err error) { func (NullStorage) CreateRefreshTokenSession(_ context.Context, _ string, _ fosite.Requester) (err error) {
@ -29,11 +29,11 @@ func (NullStorage) CreateRefreshTokenSession(_ context.Context, _ string, _ fosi
} }
func (NullStorage) GetRefreshTokenSession(_ context.Context, _ string, _ fosite.Session) (request fosite.Requester, err error) { func (NullStorage) GetRefreshTokenSession(_ context.Context, _ string, _ fosite.Session) (request fosite.Requester, err error) {
return nil, errNotImplemented return nil, errNullStorageNotImplemented
} }
func (NullStorage) DeleteRefreshTokenSession(_ context.Context, _ string) (err error) { func (NullStorage) DeleteRefreshTokenSession(_ context.Context, _ string) (err error) {
return errNotImplemented return errNullStorageNotImplemented
} }
func (NullStorage) CreateAccessTokenSession(_ context.Context, _ string, _ fosite.Requester) (err error) { func (NullStorage) CreateAccessTokenSession(_ context.Context, _ string, _ fosite.Requester) (err error) {
@ -41,11 +41,11 @@ func (NullStorage) CreateAccessTokenSession(_ context.Context, _ string, _ fosit
} }
func (NullStorage) GetAccessTokenSession(_ context.Context, _ string, _ fosite.Session) (request fosite.Requester, err error) { func (NullStorage) GetAccessTokenSession(_ context.Context, _ string, _ fosite.Session) (request fosite.Requester, err error) {
return nil, errNotImplemented return nil, errNullStorageNotImplemented
} }
func (NullStorage) DeleteAccessTokenSession(_ context.Context, _ string) (err error) { func (NullStorage) DeleteAccessTokenSession(_ context.Context, _ string) (err error) {
return errNotImplemented return errNullStorageNotImplemented
} }
func (NullStorage) CreateOpenIDConnectSession(_ context.Context, _ string, _ fosite.Requester) error { func (NullStorage) CreateOpenIDConnectSession(_ context.Context, _ string, _ fosite.Requester) error {
@ -53,15 +53,15 @@ func (NullStorage) CreateOpenIDConnectSession(_ context.Context, _ string, _ fos
} }
func (NullStorage) GetOpenIDConnectSession(_ context.Context, _ string, _ fosite.Requester) (fosite.Requester, error) { func (NullStorage) GetOpenIDConnectSession(_ context.Context, _ string, _ fosite.Requester) (fosite.Requester, error) {
return nil, errNotImplemented return nil, errNullStorageNotImplemented
} }
func (NullStorage) DeleteOpenIDConnectSession(_ context.Context, _ string) error { func (NullStorage) DeleteOpenIDConnectSession(_ context.Context, _ string) error {
return errNotImplemented return errNullStorageNotImplemented
} }
func (NullStorage) GetPKCERequestSession(_ context.Context, _ string, _ fosite.Session) (fosite.Requester, error) { func (NullStorage) GetPKCERequestSession(_ context.Context, _ string, _ fosite.Session) (fosite.Requester, error) {
return nil, errNotImplemented return nil, errNullStorageNotImplemented
} }
func (NullStorage) CreatePKCERequestSession(_ context.Context, _ string, _ fosite.Requester) error { func (NullStorage) CreatePKCERequestSession(_ context.Context, _ string, _ fosite.Requester) error {
@ -69,7 +69,7 @@ func (NullStorage) CreatePKCERequestSession(_ context.Context, _ string, _ fosit
} }
func (NullStorage) DeletePKCERequestSession(_ context.Context, _ string) error { func (NullStorage) DeletePKCERequestSession(_ context.Context, _ string) error {
return errNotImplemented return errNullStorageNotImplemented
} }
func (NullStorage) CreateAuthorizeCodeSession(_ context.Context, _ string, _ fosite.Requester) (err error) { func (NullStorage) CreateAuthorizeCodeSession(_ context.Context, _ string, _ fosite.Requester) (err error) {
@ -77,11 +77,11 @@ func (NullStorage) CreateAuthorizeCodeSession(_ context.Context, _ string, _ fos
} }
func (NullStorage) GetAuthorizeCodeSession(_ context.Context, _ string, _ fosite.Session) (request fosite.Requester, err error) { func (NullStorage) GetAuthorizeCodeSession(_ context.Context, _ string, _ fosite.Session) (request fosite.Requester, err error) {
return nil, errNotImplemented return nil, errNullStorageNotImplemented
} }
func (NullStorage) InvalidateAuthorizeCodeSession(_ context.Context, _ string) (err error) { func (NullStorage) InvalidateAuthorizeCodeSession(_ context.Context, _ string) (err error) {
return errNotImplemented return errNullStorageNotImplemented
} }
func (NullStorage) GetClient(_ context.Context, id string) (fosite.Client, error) { func (NullStorage) GetClient(_ context.Context, id string) (fosite.Client, error) {
@ -93,9 +93,9 @@ func (NullStorage) GetClient(_ context.Context, id string) (fosite.Client, error
} }
func (NullStorage) ClientAssertionJWTValid(_ context.Context, _ string) error { func (NullStorage) ClientAssertionJWTValid(_ context.Context, _ string) error {
return errNotImplemented return errNullStorageNotImplemented
} }
func (NullStorage) SetClientAssertionJWT(_ context.Context, _ string, _ time.Time) error { func (NullStorage) SetClientAssertionJWT(_ context.Context, _ string, _ time.Time) error {
return errNotImplemented return errNullStorageNotImplemented
} }

View File

@ -17,7 +17,7 @@ import (
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"go.pinniped.dev/internal/fosite/authorizationcode" "go.pinniped.dev/internal/fositestorage/authorizationcode"
"go.pinniped.dev/test/library" "go.pinniped.dev/test/library"
) )
@ -29,7 +29,7 @@ func TestAuthorizeCodeStorage(t *testing.T) {
// randomly generated HMAC authorization code (see below) // randomly generated HMAC authorization code (see below)
code = "TQ72B8YjdEOZyxridYbTLE-pzoK4hpdkZxym5j4EmSc.TKRTgQG41IBQ16FDKTthRdhXfLlNaErcMd9Fy47uXAw" code = "TQ72B8YjdEOZyxridYbTLE-pzoK4hpdkZxym5j4EmSc.TKRTgQG41IBQ16FDKTthRdhXfLlNaErcMd9Fy47uXAw"
// name of the secret that will be created in Kube // name of the secret that will be created in Kube
name = "pinniped-storage-authorization-codes-jssfhaibxdkiaugxufbsso3bixmfo7fzjvuevxbr35c4xdxolqga" name = "pinniped-storage-authcode-jssfhaibxdkiaugxufbsso3bixmfo7fzjvuevxbr35c4xdxolqga"
) )
hmac := compose.NewOAuth2HMACStrategy(&compose.Config{}, []byte("super-secret-32-byte-for-testing"), nil) hmac := compose.NewOAuth2HMACStrategy(&compose.Config{}, []byte("super-secret-32-byte-for-testing"), nil)