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: jackson provider #4242

Merged
merged 16 commits into from
Dec 19, 2024
5 changes: 5 additions & 0 deletions driver/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ const (
ViperKeyIgnoreNetworkErrors = "selfservice.methods.password.config.ignore_network_errors"
ViperKeyTOTPIssuer = "selfservice.methods.totp.config.issuer"
ViperKeyOIDCBaseRedirectURL = "selfservice.methods.oidc.config.base_redirect_uri"
ViperKeySAMLBaseRedirectURL = "selfservice.methods.saml.config.base_redirect_uri"
ViperKeyWebAuthnRPDisplayName = "selfservice.methods.webauthn.config.rp.display_name"
ViperKeyWebAuthnRPID = "selfservice.methods.webauthn.config.rp.id"
ViperKeyWebAuthnRPOrigin = "selfservice.methods.webauthn.config.rp.origin"
Expand Down Expand Up @@ -616,6 +617,10 @@ func (p *Config) OIDCRedirectURIBase(ctx context.Context) *url.URL {
return p.GetProvider(ctx).URIF(ViperKeyOIDCBaseRedirectURL, p.SelfPublicURL(ctx))
}

func (p *Config) SAMLRedirectURIBase(ctx context.Context) *url.URL {
return p.GetProvider(ctx).URIF(ViperKeySAMLBaseRedirectURL, p.SelfPublicURL(ctx))
}

func (p *Config) IdentityTraitsSchemas(ctx context.Context) (ss Schemas, err error) {
if err = p.GetProvider(ctx).Koanf.Unmarshal(ViperKeyIdentitySchemas, &ss); err != nil {
return ss, nil
Expand Down
85 changes: 85 additions & 0 deletions embedx/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,49 @@
}
]
},
"selfServiceSAMLProvider": {
"type": "object",
"properties": {
"id": {
"type": "string",
"examples": ["be2b30", "my-saml-provider"]
},
"label": {
"title": "Optional string which will be used when generating labels for UI buttons.",
"type": "string"
},
"mapper_url": {
"title": "Jsonnet Mapper URL",
"description": "The URL where the jsonnet source is located for mapping the provider's data to Ory Kratos data.",
"type": "string",
"format": "uri",
"examples": [
"file://path/to/oidc.jsonnet",
"https://foo.bar.com/path/to/oidc.jsonnet",
"base64://bG9jYWwgc3ViamVjdCA9I..."
]
},
"raw_idp_metadata_xml": {
"title": "Raw IDP Metadata XML",
"description": "The raw IDP metadata XML for the SAML provider.",
"type": "string",
"format": "uri",
"examples": [
"file://path/to/metadata.xml",
"https://foo.bar.com/path/to/metadata.xml",
"base64://bG9jYWwgc3ViamVjdCA9I..."
]
},
"organization_id": {
"title": "Organization ID",
"description": "The ID of the organization that this provider belongs to. Only effective in the Ory Network.",
"type": "string",
"examples": ["12345678-1234-1234-1234-123456789012"]
}
},
"additionalProperties": true,
"required": ["id", "raw_idp_metadata_xml", "mapper_url"]
},
"selfServiceHooks": {
"type": "array",
"items": {
Expand Down Expand Up @@ -867,6 +910,9 @@
"oidc": {
"$ref": "#/definitions/selfServiceAfterSettingsAuthMethod"
},
"saml": {
"$ref": "#/definitions/selfServiceAfterSettingsAuthMethod"
},
"webauthn": {
"$ref": "#/definitions/selfServiceAfterSettingsAuthMethod"
},
Expand Down Expand Up @@ -912,6 +958,9 @@
"oidc": {
"$ref": "#/definitions/selfServiceAfterOIDCLoginMethod"
},
"saml": {
"$ref": "#/definitions/selfServiceAfterOIDCLoginMethod"
},
"code": {
"$ref": "#/definitions/selfServiceAfterDefaultLoginMethod"
},
Expand Down Expand Up @@ -1005,6 +1054,9 @@
"oidc": {
"$ref": "#/definitions/selfServiceAfterRegistrationMethod"
},
"saml": {
"$ref": "#/definitions/selfServiceAfterRegistrationMethod"
},
"code": {
"$ref": "#/definitions/selfServiceAfterRegistrationMethod"
},
Expand Down Expand Up @@ -1962,6 +2014,39 @@
}
}
}
},
"saml": {
"type": "object",
"title": "Specify SAML configuration",
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean",
"title": "Enables the SAML method",
"default": false
},
"config": {
"type": "object",
"additionalProperties": false,
"properties": {
"base_redirect_uri": {
"type": "string",
"title": "Base URL for SAML Redirect URIs",
"description": "Can be used to modify the base URL for SAML Redirect URLs. If unset, the Public Base URL will be used.",
"format": "uri",
"examples": ["https://auth.myexample.org/"]
},
"providers": {
"title": "SAML Providers",
"description": "A list and configuration of SAML providers Ory Kratos should integrate with.",
"type": "array",
"items": {
"$ref": "#/definitions/selfServiceSAMLProvider"
}
}
}
}
}
}
}
}
Expand Down
6 changes: 2 additions & 4 deletions embedx/embedx.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@ package embedx

import (
"bytes"
_ "embed"
"io"

"github.com/pkg/errors"

"github.com/ory/x/otelx"

"github.com/tidwall/gjson"

_ "embed"
"github.com/ory/x/otelx"
)

//go:embed config.schema.json
Expand Down
4 changes: 3 additions & 1 deletion identity/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const (
CredentialsTypeCodeAuth CredentialsType = "code"
CredentialsTypePasskey CredentialsType = "passkey"
CredentialsTypeProfile CredentialsType = "profile"
CredentialsTypeSAML CredentialsType = "saml"
)

func (c CredentialsType) String() string {
Expand All @@ -99,7 +100,7 @@ func (c CredentialsType) ToUiNodeGroup() node.UiNodeGroup {
switch c {
case CredentialsTypePassword:
return node.PasswordGroup
case CredentialsTypeOIDC:
case CredentialsTypeOIDC, CredentialsTypeSAML:
return node.OpenIDConnectGroup
case CredentialsTypeTOTP:
return node.TOTPGroup
Expand Down Expand Up @@ -138,6 +139,7 @@ func ParseCredentialsType(in string) (CredentialsType, bool) {
for _, t := range []CredentialsType{
CredentialsTypePassword,
CredentialsTypeOIDC,
CredentialsTypeSAML,
CredentialsTypeTOTP,
CredentialsTypeLookup,
CredentialsTypeWebAuthn,
Expand Down
1 change: 1 addition & 0 deletions internal/client-go/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
2 changes: 1 addition & 1 deletion persistence/sql/identity/persister_identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -1337,7 +1337,7 @@ func FindIdentityCredentialsTypeByName(con *pop.Connection, ct identity.Credenti
}

if !found {
return uuid.Nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("The SQL adapter failed to return the appropriate credentials_type for nane %s. This is a bug in the code.", ct))
return uuid.Nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("The SQL adapter failed to return the appropriate credentials_type for name %q. This is a bug in the code.", ct))
}

return result, nil
Expand Down
2 changes: 1 addition & 1 deletion selfservice/flow/login/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@
// We only apply the filter on AAL1, because the OIDC strategy can only satsify
// AAL1.
strategyFilters = []StrategyFilter{func(s Strategy) bool {
return s.ID() == identity.CredentialsTypeOIDC
return s.ID() == identity.CredentialsTypeOIDC || s.ID() == identity.CredentialsTypeSAML

Check warning on line 236 in selfservice/flow/login/handler.go

View check run for this annotation

Codecov / codecov/patch

selfservice/flow/login/handler.go#L236

Added line #L236 was not covered by tests
}}
}
}
Expand Down
4 changes: 3 additions & 1 deletion selfservice/flow/registration/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,9 @@
h.d.Logger().WithError(err).Warnf("ignoring invalid UUID %q in query parameter `organization`", rawOrg)
} else {
f.OrganizationID = uuid.NullUUID{UUID: orgID, Valid: true}
strategyFilters = []StrategyFilter{func(s Strategy) bool { return s.ID() == identity.CredentialsTypeOIDC }}
strategyFilters = []StrategyFilter{func(s Strategy) bool {
return s.ID() == identity.CredentialsTypeOIDC || s.ID() == identity.CredentialsTypeSAML
}}

Check warning on line 146 in selfservice/flow/registration/handler.go

View check run for this annotation

Codecov / codecov/patch

selfservice/flow/registration/handler.go#L144-L146

Added lines #L144 - L146 were not covered by tests
}
}
for _, s := range h.d.RegistrationStrategies(r.Context(), strategyFilters...) {
Expand Down
1 change: 1 addition & 0 deletions selfservice/strategy/oidc/provider_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ var supportedProviders = map[string]func(config *Configuration, reg Dependencies
"patreon": NewProviderPatreon,
"lark": NewProviderLark,
"x": NewProviderX,
"jackson": NewProviderJackson,
}

func (c ConfigurationCollection) Provider(id string, reg Dependencies) (Provider, error) {
Expand Down
57 changes: 57 additions & 0 deletions selfservice/strategy/oidc/provider_jackson.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0

package oidc

import (
"context"
"strings"

"github.com/coreos/go-oidc/v3/oidc"
"golang.org/x/oauth2"

"github.com/ory/x/urlx"
)

type ProviderJackson struct {
*ProviderGenericOIDC
}

func NewProviderJackson(
config *Configuration,
reg Dependencies,
) Provider {
return &ProviderJackson{
ProviderGenericOIDC: &ProviderGenericOIDC{
config: config,
reg: reg,
},
}
}

func (j *ProviderJackson) setProvider(ctx context.Context) {
if j.ProviderGenericOIDC.p == nil {
internalHost := strings.TrimSuffix(j.config.TokenURL, "/api/oauth/token")
config := oidc.ProviderConfig{
IssuerURL: j.config.IssuerURL,
AuthURL: j.config.AuthURL,
TokenURL: j.config.TokenURL,
DeviceAuthURL: "",
UserInfoURL: internalHost + "/api/oauth/userinfo",
JWKSURL: internalHost + "/oauth/jwks",
Algorithms: []string{"RS256"},
}
j.ProviderGenericOIDC.p = config.NewProvider(j.withHTTPClientContext(ctx))
}
}

func (j *ProviderJackson) OAuth2(ctx context.Context) (*oauth2.Config, error) {
j.setProvider(ctx)
endpoint := j.ProviderGenericOIDC.p.Endpoint()
config := j.oauth2ConfigFromEndpoint(ctx, endpoint)
config.RedirectURL = urlx.AppendPaths(
j.reg.Config().SAMLRedirectURIBase(ctx),
"/self-service/methods/saml/callback/"+j.config.ID).String()

return config, nil
}
36 changes: 36 additions & 0 deletions selfservice/strategy/oidc/provider_jackson_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright © 2024 Ory Corp
// SPDX-License-Identifier: Apache-2.0

package oidc_test

import (
"context"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/ory/kratos/internal"
"github.com/ory/kratos/selfservice/strategy/oidc"
)

func TestProviderJackson(t *testing.T) {
_, reg := internal.NewVeryFastRegistryWithoutDB(t)

j := oidc.NewProviderJackson(&oidc.Configuration{
Provider: "jackson",
IssuerURL: "https://www.jackson.com/oauth",
AuthURL: "https://www.jackson.com/oauth/auth",
TokenURL: "https://www.jackson.com/api/oauth/token",
Mapper: "file://./stub/hydra.schema.json",
Scope: []string{"email", "profile"},
ID: "some-id",
}, reg)
assert.NotNil(t, j)

c, err := j.(oidc.OAuth2Provider).OAuth2(context.Background())
require.NoError(t, err)

assert.True(t, strings.HasSuffix(c.RedirectURL, "/self-service/methods/saml/callback/some-id"))
}
24 changes: 18 additions & 6 deletions selfservice/strategy/oidc/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
"github.com/ory/kratos/x"
"github.com/ory/x/decoderx"
"github.com/ory/x/jsonnetsecure"
"github.com/ory/x/jsonx"
"github.com/ory/x/otelx"
"github.com/ory/x/sqlxx"
"github.com/ory/x/stringsx"
Expand Down Expand Up @@ -122,6 +121,7 @@
d Dependencies
validator *schema.Validator
dec *decoderx.HTTP
credType identity.CredentialsType
}

type AuthCodeContainer struct {
Expand Down Expand Up @@ -203,15 +203,27 @@
http.Redirect(w, r, dest.String(), http.StatusFound)
}

func NewStrategy(d any) *Strategy {
return &Strategy{
type NewStrategyOpt func(s *Strategy)

func ForCredentialType(ct identity.CredentialsType) NewStrategyOpt {
return func(s *Strategy) { s.credType = ct }

Check warning on line 209 in selfservice/strategy/oidc/strategy.go

View check run for this annotation

Codecov / codecov/patch

selfservice/strategy/oidc/strategy.go#L208-L209

Added lines #L208 - L209 were not covered by tests
}

func NewStrategy(d any, opts ...NewStrategyOpt) *Strategy {
s := &Strategy{
d: d.(Dependencies),
validator: schema.NewValidator(),
credType: identity.CredentialsTypeOIDC,
}
for _, opt := range opts {
opt(s)

Check warning on line 219 in selfservice/strategy/oidc/strategy.go

View check run for this annotation

Codecov / codecov/patch

selfservice/strategy/oidc/strategy.go#L219

Added line #L219 was not covered by tests
}

return s
}

func (s *Strategy) ID() identity.CredentialsType {
return identity.CredentialsTypeOIDC
return s.credType
}

func (s *Strategy) validateFlow(ctx context.Context, r *http.Request, rid uuid.UUID) (flow.Flow, error) {
Expand Down Expand Up @@ -516,8 +528,8 @@
var c ConfigurationCollection

conf := s.d.Config().SelfServiceStrategy(ctx, string(s.ID())).Config
if err := jsonx.
NewStrictDecoder(bytes.NewBuffer(conf)).
if err := json.
NewDecoder(bytes.NewBuffer(conf)).
Decode(&c); err != nil {
s.d.Logger().WithError(err).WithField("config", conf)
return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to decode OpenID Connect Provider configuration: %s", err))
Expand Down
Loading
Loading