Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
166 lines
6.2 KiB
Go
166 lines
6.2 KiB
Go
// Copyright © 2023 Ory Corp
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package oidc
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/dghubble/oauth1"
|
|
"github.com/pkg/errors"
|
|
"golang.org/x/oauth2"
|
|
|
|
"github.com/ory/herodot"
|
|
"github.com/ory/kratos/selfservice/strategy/oidc/oidcerr"
|
|
"github.com/ory/kratos/x"
|
|
)
|
|
|
|
type (
|
|
Provider interface {
|
|
Config() *Configuration
|
|
}
|
|
OAuth2Provider interface {
|
|
Provider
|
|
AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption
|
|
OAuth2(ctx context.Context) (*oauth2.Config, error)
|
|
Claims(ctx context.Context, exchange *oauth2.Token, query url.Values) (*Claims, error)
|
|
}
|
|
OAuth1Provider interface {
|
|
Provider
|
|
OAuth1(ctx context.Context) *oauth1.Config
|
|
AuthURL(ctx context.Context, state string) (string, error)
|
|
Claims(ctx context.Context, token *oauth1.Token) (*Claims, error)
|
|
ExchangeToken(ctx context.Context, req *http.Request) (*oauth1.Token, error)
|
|
}
|
|
)
|
|
|
|
type OAuth2TokenExchanger interface {
|
|
Exchange(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error)
|
|
}
|
|
|
|
type IDTokenVerifier interface {
|
|
Verify(ctx context.Context, rawIDToken string) (*Claims, error)
|
|
}
|
|
|
|
type NonceValidationSkipper interface {
|
|
CanSkipNonce(*Claims) bool
|
|
}
|
|
|
|
type Claims struct {
|
|
Issuer string `json:"iss,omitempty"`
|
|
Subject string `json:"sub,omitempty"`
|
|
Object string `json:"oid,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
GivenName string `json:"given_name,omitempty"`
|
|
FamilyName string `json:"family_name,omitempty"`
|
|
LastName string `json:"last_name,omitempty"`
|
|
MiddleName string `json:"middle_name,omitempty"`
|
|
Nickname string `json:"nickname,omitempty"`
|
|
PreferredUsername string `json:"preferred_username,omitempty"`
|
|
Profile string `json:"profile,omitempty"`
|
|
Picture string `json:"picture,omitempty"`
|
|
Website string `json:"website,omitempty"`
|
|
Email string `json:"email,omitempty"`
|
|
// ConvertibleBoolean is used as Apple casually sends the email_verified field as a string.
|
|
EmailVerified x.ConvertibleBoolean `json:"email_verified,omitempty"`
|
|
Gender string `json:"gender,omitempty"`
|
|
Birthdate string `json:"birthdate,omitempty"`
|
|
Zoneinfo string `json:"zoneinfo,omitempty"`
|
|
Locale Locale `json:"locale,omitempty"`
|
|
PhoneNumber string `json:"phone_number,omitempty"`
|
|
PhoneNumberVerified bool `json:"phone_number_verified,omitempty"`
|
|
UpdatedAt int64 `json:"updated_at,omitempty"`
|
|
HD string `json:"hd,omitempty"`
|
|
Team string `json:"team,omitempty"`
|
|
Nonce string `json:"nonce,omitempty"`
|
|
NonceSupported bool `json:"nonce_supported,omitempty"`
|
|
|
|
// ACR is the Authentication Context Class Reference reported by the
|
|
// upstream OIDC provider. See OpenID Connect Core 1.0, Section 2.
|
|
ACR string `json:"acr,omitempty"`
|
|
|
|
// AMR is the list of Authentication Methods References reported by the
|
|
// upstream OIDC provider. See OpenID Connect Core 1.0, Section 2 and
|
|
// RFC 8176.
|
|
AMR []string `json:"amr,omitempty"`
|
|
|
|
RawClaims map[string]any `json:"raw_claims,omitempty"`
|
|
}
|
|
|
|
type Locale string
|
|
|
|
func (l *Locale) UnmarshalJSON(data []byte) error {
|
|
var linkedInLocale struct {
|
|
Language string `json:"language"`
|
|
Country string `json:"country"`
|
|
}
|
|
if err := json.Unmarshal(data, &linkedInLocale); err == nil {
|
|
switch {
|
|
case linkedInLocale.Language == "":
|
|
*l = Locale(linkedInLocale.Country)
|
|
case linkedInLocale.Country == "":
|
|
*l = Locale(linkedInLocale.Language)
|
|
default:
|
|
*l = Locale(strings.Join([]string{linkedInLocale.Language, linkedInLocale.Country}, "-"))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
return json.Unmarshal(data, (*string)(l))
|
|
}
|
|
|
|
// Validate checks if the claims are valid.
|
|
func (c *Claims) Validate() error {
|
|
if c.Subject == "" {
|
|
return oidcerr.Wrap(oidcerr.StepClaimsDecode, errors.WithStack(herodot.ErrUpstreamError().WithReasonf("provider did not return a subject")))
|
|
}
|
|
if c.Issuer == "" {
|
|
return oidcerr.Wrap(oidcerr.StepClaimsDecode, errors.WithStack(herodot.ErrUpstreamError().WithReasonf("issuer not set in claims")))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UpstreamParameters returns a list of oauth2.AuthCodeOption based on the upstream parameters.
|
|
//
|
|
// Only allowed parameters are returned and the rest is ignored.
|
|
// Allowed parameters are also defined in the `oidc/.schema/link.schema.json` file, however,
|
|
// this function also validates the parameters to prevent any potential security issues.
|
|
//
|
|
// Allowed parameters are:
|
|
// - `hd` (string): The `hd` parameter limits the login/registration process to a Google Organization, e.g. `mycollege.edu`.
|
|
// - `prompt` (string): The `prompt` specifies whether the Authorization Server prompts the End-User for reauthentication and consent, e.g. `select_account`.
|
|
// - `auth_type` (string): The `auth_type` parameter specifies the requested authentication features (as a comma-separated list), e.g. `reauthenticate`.
|
|
// - `acr_values` (string): The `acr_values` specifies the Authentication Context Class Reference values for the authorization request.
|
|
//
|
|
// `login_hint` is intentionally not forwarded. A caller-supplied value cannot be
|
|
// trusted, and on IdPs that act on it (Google, Microsoft, Auth0, …) it can steer
|
|
// account, tenant, or OAuth client selection and enable a client-swap attack
|
|
// (HackerOne #3239672, ory-corp/cloud#8955).
|
|
func UpstreamParameters(upstreamParameters map[string]string) []oauth2.AuthCodeOption {
|
|
// validation of upstream parameters are already handled in the `oidc/.schema/link.schema.json` and `oidc/.schema/settings.schema.json` file.
|
|
// `upstreamParameters` will always only contain allowed parameters based on the configuration.
|
|
|
|
// we double-check the parameters here to prevent any potential security issues.
|
|
allowedParameters := map[string]struct{}{
|
|
"hd": {},
|
|
"prompt": {},
|
|
"auth_type": {},
|
|
"acr_values": {},
|
|
}
|
|
|
|
var params []oauth2.AuthCodeOption
|
|
for up, v := range upstreamParameters {
|
|
if _, ok := allowedParameters[up]; !ok {
|
|
continue
|
|
}
|
|
params = append(params, oauth2.SetAuthURLParam(up, v))
|
|
}
|
|
|
|
return params
|
|
}
|