From ff2c164057ae862f48a7d36439c1446082c27ace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=9D=A8=E6=96=87?= Date: Fri, 8 Oct 2021 14:20:45 +0800 Subject: [PATCH 1/5] fix: improve example & fix userinfo marshal (#132) * fix: example client should track state, call cli.CodeFlow need context * fix: oidc userinfo can UnmarshalJSON with address * rp Discover use client.Discover * add instruction for example to README.md --- README.md | 12 ++++++++++++ example/client/github/github.go | 2 +- example/internal/mock/storage.go | 7 ++++--- pkg/client/rp/relaying_party.go | 5 +++-- pkg/oidc/userinfo.go | 3 ++- pkg/oidc/userinfo_test.go | 26 ++++++++++++++++++++++++++ 6 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 pkg/oidc/userinfo_test.go diff --git a/README.md b/README.md index ef428ed..b90556a 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,18 @@ Whenever possible we tried to reuse / extend existing packages like `OAuth2 for Check the `/example` folder where example code for different scenarios is located. +```bash +# start oidc op server +# oidc discovery http://localhost:9998/.well-known/openid-configuration +CAOS_OIDC_DEV=1 go run github.com/caos/oidc/example/server/default +# start oidc web client +CLIENT_ID=web CLIENT_SECRET=web ISSUER=http://localhost:9998/ SCOPES=openid PORT=5556 go run github.com/caos/oidc/example/client/app +``` + +- browser http://localhost:5556/login will redirect to op server +- input id to login +- redirect to client app display user info + ## Features | | Code Flow | Implicit Flow | Hybrid Flow | Discovery | PKCE | Token Exchange | mTLS | JWT Profile | Refresh Token | diff --git a/example/client/github/github.go b/example/client/github/github.go index f39c40b..35c7723 100644 --- a/example/client/github/github.go +++ b/example/client/github/github.go @@ -43,7 +43,7 @@ func main() { state := func() string { return uuid.New().String() } - token := cli.CodeFlow(relyingParty, callbackPath, port, state) + token := cli.CodeFlow(ctx, relyingParty, callbackPath, port, state) client := github.NewClient(relyingParty.OAuthConfig().Client(ctx, token.Token)) diff --git a/example/internal/mock/storage.go b/example/internal/mock/storage.go index 214ba54..775f757 100644 --- a/example/internal/mock/storage.go +++ b/example/internal/mock/storage.go @@ -36,6 +36,7 @@ type AuthRequest struct { Nonce string ClientID string CodeChallenge *oidc.CodeChallenge + State string } func (a *AuthRequest) GetACR() string { @@ -98,7 +99,7 @@ func (a *AuthRequest) GetScopes() []string { func (a *AuthRequest) SetCurrentScopes(scopes []string) {} func (a *AuthRequest) GetState() string { - return "" + return a.State } func (a *AuthRequest) GetSubject() string { @@ -120,7 +121,7 @@ func (s *AuthStorage) Health(ctx context.Context) error { } func (s *AuthStorage) CreateAuthRequest(_ context.Context, authReq *oidc.AuthRequest, _ string) (op.AuthRequest, error) { - a = &AuthRequest{ID: "id", ClientID: authReq.ClientID, ResponseType: authReq.ResponseType, Nonce: authReq.Nonce, RedirectURI: authReq.RedirectURI} + a = &AuthRequest{ID: "id", ClientID: authReq.ClientID, ResponseType: authReq.ResponseType, Nonce: authReq.Nonce, RedirectURI: authReq.RedirectURI, State: authReq.State} if authReq.CodeChallenge != "" { a.CodeChallenge = &oidc.CodeChallenge{ Challenge: authReq.CodeChallenge, @@ -212,7 +213,7 @@ func (s *AuthStorage) GetClientByClientID(_ context.Context, id string) (op.Clie accessTokenType = op.AccessTokenTypeJWT responseTypes = []oidc.ResponseType{oidc.ResponseTypeIDToken, oidc.ResponseTypeIDTokenOnly} } - return &ConfClient{ID: id, applicationType: appType, authMethod: authMethod, accessTokenType: accessTokenType, responseTypes: responseTypes, devMode: false}, nil + return &ConfClient{ID: id, applicationType: appType, authMethod: authMethod, accessTokenType: accessTokenType, responseTypes: responseTypes, devMode: false, grantTypes: []oidc.GrantType{oidc.GrantTypeCode}}, nil } func (s *AuthStorage) AuthorizeClientIDSecret(_ context.Context, id string, _ string) error { diff --git a/pkg/client/rp/relaying_party.go b/pkg/client/rp/relaying_party.go index 669a910..b9b568d 100644 --- a/pkg/client/rp/relaying_party.go +++ b/pkg/client/rp/relaying_party.go @@ -170,17 +170,18 @@ func NewRelyingPartyOIDC(issuer, clientID, clientSecret, redirectURI string, sco return nil, err } } - endpoints, err := Discover(rp.issuer, rp.httpClient) + discoveryConfiguration, err := client.Discover(rp.issuer, rp.httpClient) if err != nil { return nil, err } + endpoints := GetEndpoints(discoveryConfiguration) rp.oauthConfig.Endpoint = endpoints.Endpoint rp.endpoints = endpoints return rp, nil } -//DefaultRPOpts is the type for providing dynamic options to the DefaultRP +//Option is the type for providing dynamic options to the DefaultRP type Option func(*relyingParty) error //WithCookieHandler set a `CookieHandler` for securing the various redirects diff --git a/pkg/oidc/userinfo.go b/pkg/oidc/userinfo.go index 6bc0016..2ae2acb 100644 --- a/pkg/oidc/userinfo.go +++ b/pkg/oidc/userinfo.go @@ -360,6 +360,7 @@ func (i *userinfo) MarshalJSON() ([]byte, error) { func (i *userinfo) UnmarshalJSON(data []byte) error { type Alias userinfo a := &struct { + Address *userInfoAddress `json:"address,omitempty"` *Alias UpdatedAt int64 `json:"update_at,omitempty"` }{ @@ -368,7 +369,7 @@ func (i *userinfo) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(data, &a); err != nil { return err } - + i.Address = a.Address i.UpdatedAt = Time(time.Unix(a.UpdatedAt, 0).UTC()) if err := json.Unmarshal(data, &i.claims); err != nil { diff --git a/pkg/oidc/userinfo_test.go b/pkg/oidc/userinfo_test.go new file mode 100644 index 0000000..c3c8b7b --- /dev/null +++ b/pkg/oidc/userinfo_test.go @@ -0,0 +1,26 @@ +package oidc + +import ( + "encoding/json" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestUserInfoMarshal(t *testing.T) { + userinfo := NewUserInfo() + userinfo.SetSubject("test") + userinfo.SetAddress(NewUserInfoAddress("Test 789\nPostfach 2", "", "", "", "", "")) + userinfo.SetEmail("test", true) + userinfo.SetPhone("0791234567", true) + userinfo.SetName("Test") + userinfo.AppendClaims("private_claim", "test") + + marshal, err := json.Marshal(userinfo) + out := NewUserInfo() + assert.NoError(t, err) + assert.NoError(t, json.Unmarshal(marshal, out)) + assert.Equal(t, userinfo.GetAddress(), out.GetAddress()) + expected, err := json.Marshal(out) + assert.NoError(t, err) + assert.Equal(t, expected, marshal) +} From eb38b7aa60c8f9c533f40e44e01fa867748853aa Mon Sep 17 00:00:00 2001 From: Livio Amstutz Date: Fri, 8 Oct 2021 08:23:53 +0200 Subject: [PATCH 2/5] chore: build on fork PRs (#133) --- .github/workflows/release.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6e4ab89..471c09e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,9 +2,12 @@ name: Release on: push: branches: - - '**' + - main tags-ignore: - '**' + pull_request: + branches: + - '**' workflow_dispatch: jobs: From 292188ba308acbfb7ca55fe5fb37e2712f9b99e9 Mon Sep 17 00:00:00 2001 From: jmillerv <50455719+jmillerv@users.noreply.github.com> Date: Sun, 10 Oct 2021 13:30:24 -0600 Subject: [PATCH 3/5] docs: fix readme typos (#134) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b90556a..eb52037 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ As of 2020 there are not a lot of `OIDC` library's in `Go` which can handle serv - [Certify this library as OP](https://openid.net/certification/#OPs) -### Other Go OpenID Connect library's +### Other Go OpenID Connect libraries [https://github.com/coreos/go-oidc](https://github.com/coreos/go-oidc) @@ -76,7 +76,7 @@ The `go-oidc` does only support `RP` and is not feasible to use as `OP` that's w [https://github.com/ory/fosite](https://github.com/ory/fosite) -We did not choose `fosite` because it implements `OAuth 2.0` on its own and does not rely in the golang provided package. Nonetheless this is a great project. +We did not choose `fosite` because it implements `OAuth 2.0` on its own and does not rely on the golang provided package. Nonetheless this is a great project. ## License From 55ec7d9dd28f825322f6d887c8f2c9535721235e Mon Sep 17 00:00:00 2001 From: Livio Amstutz Date: Tue, 26 Oct 2021 09:15:02 +0200 Subject: [PATCH 4/5] docs: remove implicit and hybrid flow from supported RP features in readme (#136) * docs: remove implicit flow from supported features in readme * docs: remove implicit flow from supported features in readme Co-authored-by: Florian Forster Co-authored-by: Florian Forster --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index eb52037..eaed6a8 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ CLIENT_ID=web CLIENT_SECRET=web ISSUER=http://localhost:9998/ SCOPES=openid PORT | | Code Flow | Implicit Flow | Hybrid Flow | Discovery | PKCE | Token Exchange | mTLS | JWT Profile | Refresh Token | |----------------|-----------|---------------|-------------|-----------|------|----------------|---------|-------------|---------------| -| Relaying Party | yes | yes | not yet | yes | yes | partial | not yet | yes | yes | +| Relaying Party | yes | no[^1] | no | yes | yes | partial | not yet | yes | yes | | Origin Party | yes | yes | not yet | yes | yes | not yet | not yet | yes | yes | ### Resources @@ -85,3 +85,6 @@ The full functionality of this library is and stays open source and free to use See the exact licensing terms [here](./LICENSE) Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + + +[^1]: https://github.com/caos/oidc/issues/135#issuecomment-950563892 From c45f03e144a1bd0420be48c0dde531cbef090b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=9D=A8=E6=96=87?= Date: Thu, 28 Oct 2021 13:06:34 +0800 Subject: [PATCH 5/5] fix: allowed ConcatenateJSON with empty input (#138) --- pkg/utils/marshal.go | 8 ++++++++ pkg/utils/marshal_test.go | 32 +++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/pkg/utils/marshal.go b/pkg/utils/marshal.go index 4f53b4e..8c04588 100644 --- a/pkg/utils/marshal.go +++ b/pkg/utils/marshal.go @@ -29,6 +29,14 @@ func ConcatenateJSON(first, second []byte) ([]byte, error) { if !bytes.HasPrefix(second, []byte{'{'}) { return nil, fmt.Errorf("jws: invalid JSON %s", second) } + // check empty + if len(first) == 2 { + return second, nil + } + if len(second) == 2 { + return first, nil + } + first[len(first)-1] = ',' first = append(first, second[1:]...) return first, nil diff --git a/pkg/utils/marshal_test.go b/pkg/utils/marshal_test.go index f9221f6..bfc8275 100644 --- a/pkg/utils/marshal_test.go +++ b/pkg/utils/marshal_test.go @@ -44,6 +44,36 @@ func TestConcatenateJSON(t *testing.T) { []byte(`{"some": "thing","another": "thing"}`), false, }, + { + "first empty", + args{ + []byte(`{}`), + []byte(`{"some": "thing"}`), + }, + + []byte(`{"some": "thing"}`), + false, + }, + { + "second empty", + args{ + []byte(`{"some": "thing"}`), + []byte(`{}`), + }, + + []byte(`{"some": "thing"}`), + false, + }, + { + "both empty", + args{ + []byte(`{}`), + []byte(`{}`), + }, + + []byte(`{}`), + false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -53,7 +83,7 @@ func TestConcatenateJSON(t *testing.T) { return } if !bytes.Equal(got, tt.want) { - t.Errorf("ConcatenateJSON() got = %v, want %v", got, tt.want) + t.Errorf("ConcatenateJSON() got = %v, want %v", string(got), tt.want) } }) }