Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow injecting extra fosite strategies #3646

Merged
merged 5 commits into from
Oct 17, 2023
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: implement Authenticate() using Kratos
This will allow resource owner password grants to authenticate
againts a Kratos user pool.

Note that we still recommend agains using the resource owner password
grant and will not support it by default in Hydra.
hperl committed Oct 17, 2023
commit 862fedc0e453977ccaba15d8147285184e6949c1
2 changes: 1 addition & 1 deletion client/manager.go
Original file line number Diff line number Diff line change
@@ -31,7 +31,7 @@ type Filter struct {
type Manager interface {
Storage

Authenticate(ctx context.Context, id string, secret []byte) (*Client, error)
AuthenticateClient(ctx context.Context, id string, secret []byte) (*Client, error)
}

type Storage interface {
4 changes: 2 additions & 2 deletions client/manager_test_helpers.go
Original file line number Diff line number Diff line change
@@ -52,11 +52,11 @@ func TestHelperClientAuthenticate(k string, m Manager) func(t *testing.T) {
RedirectURIs: []string{"http://redirect"},
}))

c, err := m.Authenticate(ctx, "1234321", []byte("secret1"))
c, err := m.AuthenticateClient(ctx, "1234321", []byte("secret1"))
require.Error(t, err)
require.Nil(t, c)

c, err = m.Authenticate(ctx, "1234321", []byte("secret"))
c, err = m.AuthenticateClient(ctx, "1234321", []byte("secret"))
require.NoError(t, err)
assert.Equal(t, "1234321", c.GetID())
}
7 changes: 7 additions & 0 deletions driver/config/provider.go
Original file line number Diff line number Diff line change
@@ -86,6 +86,7 @@ const (
KeyAdminURL = "urls.self.admin"
KeyIssuerURL = "urls.self.issuer"
KeyIdentityProviderAdminURL = "urls.identity_provider.url"
KeyIdentityProviderPublicURL = "urls.identity_provider.publicUrl"
KeyIdentityProviderHeaders = "urls.identity_provider.headers"
KeyAccessTokenStrategy = "strategies.access_token"
KeyJWTScopeClaimStrategy = "strategies.jwt.scope_claim"
@@ -415,6 +416,12 @@ func (p *DefaultProvider) KratosAdminURL(ctx context.Context) (*url.URL, bool) {

return u, u != nil
}
func (p *DefaultProvider) KratosPublicURL(ctx context.Context) (*url.URL, bool) {
u := p.getProvider(ctx).RequestURIF(KeyIdentityProviderPublicURL, nil)

return u, u != nil
}

func (p *DefaultProvider) KratosRequestHeader(ctx context.Context) http.Header {
hh := map[string]string{}
if err := p.getProvider(ctx).Unmarshal(KeyIdentityProviderHeaders, &hh); err != nil {
2 changes: 1 addition & 1 deletion internal/httpclient/api_oidc.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion internal/kratos/fake_kratos.go
Original file line number Diff line number Diff line change
@@ -22,13 +22,17 @@ func NewFake() *FakeKratos {
return &FakeKratos{}
}

func (f *FakeKratos) DisableSession(ctx context.Context, identityProviderSessionID string) error {
func (f *FakeKratos) DisableSession(_ context.Context, identityProviderSessionID string) error {
f.DisableSessionWasCalled = true
f.LastDisabledSession = identityProviderSessionID

return nil
}

func (f *FakeKratos) Authenticate(context.Context, string, string) error {
panic("missing")
}

func (f *FakeKratos) Reset() {
f.DisableSessionWasCalled = false
f.LastDisabledSession = ""
43 changes: 42 additions & 1 deletion internal/kratos/kratos.go
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ import (
"fmt"
"net/url"

"github.com/pkg/errors"
"go.opentelemetry.io/otel/attribute"

"github.com/ory/hydra/v2/driver/config"
@@ -29,6 +30,7 @@ type (
}
Client interface {
DisableSession(ctx context.Context, identityProviderSessionID string) error
Authenticate(ctx context.Context, name, secret string) error
}
Default struct {
dependencies
@@ -39,6 +41,37 @@ func New(d dependencies) Client {
return &Default{dependencies: d}
}

func (k *Default) Authenticate(ctx context.Context, name, secret string) (err error) {
hperl marked this conversation as resolved.
Show resolved Hide resolved
ctx, span := k.Tracer(ctx).Tracer().Start(ctx, "kratos.Authenticate")
otelx.End(span, &err)

publicURL, ok := k.Config().KratosPublicURL(ctx)
span.SetAttributes(attribute.String("public_url", fmt.Sprintf("%+v", publicURL)))
if !ok {
span.SetAttributes(attribute.Bool("skipped", true))
span.SetAttributes(attribute.String("reason", "kratos public url not set"))

return errors.New("kratos public url not set")
}

kratos := k.newKratosClient(ctx, publicURL)

flow, _, err := kratos.FrontendApi.CreateNativeLoginFlow(ctx).Execute()
if err != nil {
return err
}

_, _, err = kratos.FrontendApi.UpdateLoginFlow(ctx).Flow(flow.Id).UpdateLoginFlowBody(client.UpdateLoginFlowBody{
UpdateLoginFlowWithPasswordMethod: &client.UpdateLoginFlowWithPasswordMethod{
Method: "password",
Identifier: name,
Password: secret,
},
}).Execute()

return err
}

func (k *Default) DisableSession(ctx context.Context, identityProviderSessionID string) (err error) {
ctx, span := k.Tracer(ctx).Tracer().Start(ctx, "kratos.DisableSession")
otelx.End(span, &err)
@@ -67,7 +100,6 @@ func (k *Default) DisableSession(ctx context.Context, identityProviderSessionID
_, err = kratos.IdentityApi.DisableSession(ctx, identityProviderSessionID).Execute()

return err

}

func (k *Default) clientConfiguration(ctx context.Context, adminURL *url.URL) *client.Configuration {
@@ -77,3 +109,12 @@ func (k *Default) clientConfiguration(ctx context.Context, adminURL *url.URL) *c

return configuration
}

func (k *Default) newKratosClient(ctx context.Context, publicURL *url.URL) *client.APIClient {
configuration := k.clientConfiguration(ctx, publicURL)
if header := k.Config().KratosRequestHeader(ctx); header != nil {
configuration.HTTPClient.Transport = httpx.WrapTransportWithHeader(configuration.HTTPClient.Transport, header)
}
kratos := client.NewAPIClient(configuration)
return kratos
}
2 changes: 2 additions & 0 deletions persistence/sql/persister.go
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@ import (
"github.com/ory/fosite/storage"
"github.com/ory/hydra/v2/aead"
"github.com/ory/hydra/v2/driver/config"
"github.com/ory/hydra/v2/internal/kratos"
"github.com/ory/hydra/v2/persistence"
"github.com/ory/hydra/v2/x"
"github.com/ory/x/contextx"
@@ -51,6 +52,7 @@ type (
ClientHasher() fosite.Hasher
KeyCipher() *aead.AESGCM
FlowCipher() *aead.XChaCha20Poly1305
Kratos() kratos.Client
contextx.Provider
x.RegistryLogger
x.TracingProvider
7 changes: 7 additions & 0 deletions persistence/sql/persister_authenticate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package sql

import "context"

func (p *Persister) Authenticate(ctx context.Context, name, secret string) error {
return p.r.Kratos().Authenticate(ctx, name, secret)
}
4 changes: 2 additions & 2 deletions persistence/sql/persister_client.go
Original file line number Diff line number Diff line change
@@ -76,8 +76,8 @@ func (p *Persister) UpdateClient(ctx context.Context, cl *client.Client) (err er
})
}

func (p *Persister) Authenticate(ctx context.Context, id string, secret []byte) (_ *client.Client, err error) {
ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.Authenticate")
func (p *Persister) AuthenticateClient(ctx context.Context, id string, secret []byte) (_ *client.Client, err error) {
ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.AuthenticateClient")
defer otelx.End(span, &err)

c, err := p.GetConcreteClient(ctx, id)
4 changes: 2 additions & 2 deletions persistence/sql/persister_nid_test.go
Original file line number Diff line number Diff line change
@@ -158,11 +158,11 @@ func (s *PersisterTestSuite) TestAuthenticate() {
client := &client.Client{ID: "client-id", Secret: "secret"}
require.NoError(t, r.Persister().CreateClient(s.t1, client))

actual, err := r.Persister().Authenticate(s.t2, "client-id", []byte("secret"))
actual, err := r.Persister().AuthenticateClient(s.t2, "client-id", []byte("secret"))
require.Error(t, err)
require.Nil(t, actual)

actual, err = r.Persister().Authenticate(s.t1, "client-id", []byte("secret"))
actual, err = r.Persister().AuthenticateClient(s.t1, "client-id", []byte("secret"))
require.NoError(t, err)
require.NotNil(t, actual)
})
8 changes: 8 additions & 0 deletions spec/config.json
Original file line number Diff line number Diff line change
@@ -834,6 +834,14 @@
"https://kratos.example.com/admin"
]
},
"publicUrl": {
"title": "The public URL of the ORY Kratos instance.",
"type": "string",
"format": "uri",
"examples": [
"https://kratos.example.com/public"
]
},
"headers": {
"title": "HTTP Request Headers",
"description": "These headers will be passed in HTTP requests to the Identity Provider.",
1 change: 1 addition & 0 deletions x/fosite_storer.go
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@ type FositeStorer interface {
pkce.PKCERequestStorage
rfc7523.RFC7523KeyStorage
verifiable.NonceManager
oauth2.ResourceOwnerPasswordCredentialsGrantStorage

RevokeRefreshToken(ctx context.Context, requestID string) error