Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
12 changes: 6 additions & 6 deletions .docker/Dockerfile-build
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ WORKDIR /go/src/github.com/ory/kratos

COPY oryx/go.mod oryx/go.mod
COPY oryx/go.sum oryx/go.sum


COPY go.mod go.mod
COPY go.sum go.sum
COPY pkg/client-go/go.* pkg/client-go/

ENV CGO_ENABLED 1
ENV CGO_CPPFLAGS -DSQLITE_DEFAULT_FILE_PERMISSIONS=0600
ENV CGO_ENABLED=1
ENV CGO_CPPFLAGS="-DSQLITE_DEFAULT_FILE_PERMISSIONS=0600"

RUN go mod download

Expand All @@ -24,12 +24,12 @@ ARG VERSION
ARG COMMIT
ARG BUILD_DATE

RUN --mount=type=cache,target=/root/.cache/go-build go build -tags sqlite \
-ldflags="-X 'github.com/ory/kratos/driver/config.Version=${VERSION}' -X 'github.com/ory/kratos/driver/config.Date=${BUILD_DATE}' -X 'github.com/ory/kratos/driver/config.Commit=${COMMIT}'" \
RUN --mount=type=cache,target=/root/.cache/go-build go build -tags sqlite,netgo \
-ldflags="-linkmode external -extldflags '-static' -X 'github.com/ory/kratos/driver/config.Version=${VERSION}' -X 'github.com/ory/kratos/driver/config.Date=${BUILD_DATE}' -X 'github.com/ory/kratos/driver/config.Commit=${COMMIT}'" \
-o /usr/bin/kratos

#########################
FROM gcr.io/distroless/base-nossl-debian12:nonroot AS runner
FROM gcr.io/distroless/static-debian12:nonroot AS runner

COPY --from=builder --chown=nonroot:nonroot /var/lib/sqlite /var/lib/sqlite
COPY --from=builder --chown=nonroot:nonroot /usr/bin/kratos /usr/bin/kratos
Expand Down
18 changes: 10 additions & 8 deletions .github/workflows/cve-scan.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:
- name: Build images
shell: bash
run: |
IMAGE_TAG="${{ env.SHA_SHORT }}" make docker
IMAGE_TAG="${{ env.SHA_SHORT }}" DOCKER_BUILD_FLAGS="--load" make docker

- name: Login to GitHub Container Registry
uses: docker/login-action@v4
Expand Down Expand Up @@ -84,15 +84,17 @@ jobs:
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: ${{ steps.grype-scan.outputs.sarif }}
- name: Install Kubescape
run: |
curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | bash
echo "$HOME/.kubescape/bin" >> "$GITHUB_PATH"
- name: Kubescape scanner
uses: kubescape/github-action@main
id: kubescape
with:
image: ${{ env.IMAGE_NAME }}
verbose: true
format: pretty-printer
# can't whitelist CVE yet: https://github.com/kubescape/kubescape/pull/1568
severityThreshold: critical
run: |
kubescape scan image ${{ env.IMAGE_NAME }} \
--severity-threshold critical \
--format pretty-printer \
--verbose
- name: Trivy Scanner
uses: aquasecurity/trivy-action@master
if: ${{ always() }}
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ format: .bin/ory node_modules .bin/buf
# Build local docker image
.PHONY: docker
docker:
DOCKER_BUILDKIT=1 DOCKER_CONTENT_TRUST=1 docker build -f .docker/Dockerfile-build --build-arg=COMMIT=$(VCS_REF) --build-arg=BUILD_DATE=$(BUILD_DATE) -t oryd/kratos:${IMAGE_TAG} .
DOCKER_BUILDKIT=1 DOCKER_CONTENT_TRUST=1 docker build -f .docker/Dockerfile-build --build-arg=COMMIT=$(VCS_REF) --build-arg=BUILD_DATE=$(BUILD_DATE) $(DOCKER_BUILD_FLAGS) -t oryd/kratos:${IMAGE_TAG} .

.PHONY: test-e2e
test-e2e: node_modules test-resetdb kratos-config-e2e
Expand Down
5 changes: 3 additions & 2 deletions embedx/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@
},
"provider": {
"title": "Provider",
"description": "Can be one of github, github-app, gitlab, generic, google, microsoft, discord, salesforce, slack, facebook, auth0, vk, yandex, apple, spotify, netid, dingtalk, patreon, amazon.",
"description": "Can be one of github, github-app, gitlab, generic, google, microsoft, discord, salesforce, slack, facebook, auth0, vk, yandex, apple, spotify, netid, dingtalk, patreon, amazon, uaepass.",
"type": "string",
"enum": [
"github",
Expand All @@ -463,7 +463,8 @@
"lark",
"x",
"fedcm-test",
"amazon"
"amazon",
"uaepass"
],
"examples": ["google"]
},
Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ require (
github.com/jarcoal/httpmock v1.3.1
github.com/jmoiron/sqlx v1.4.0
github.com/knadh/koanf/parsers/json v0.1.0
github.com/laher/mergefs v0.1.2-0.20230223191438-d16611b2f4e7 // indirect
github.com/lestrrat-go/jwx/v2 v2.1.1
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
github.com/montanaflynn/stats v0.7.1
Expand Down
5 changes: 0 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -474,8 +474,6 @@ github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP
github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY=
github.com/knadh/koanf/providers/posflag v0.1.0 h1:mKJlLrKPcAP7Ootf4pBZWJ6J+4wHYujwipe7Ie3qW6U=
github.com/knadh/koanf/providers/posflag v0.1.0/go.mod h1:SYg03v/t8ISBNrMBRMlojH8OsKowbkXV7giIbBVgbz0=
github.com/knadh/koanf/providers/rawbytes v0.1.0 h1:dpzgu2KO6uf6oCb4aP05KDmKmAmI51k5pe8RYKQ0qME=
github.com/knadh/koanf/providers/rawbytes v0.1.0/go.mod h1:mMTB1/IcJ/yE++A2iEZbY1MLygX7vttU+C+S/YmPu9c=
github.com/knadh/koanf/v2 v2.2.2 h1:ghbduIkpFui3L587wavneC9e3WIliCgiCgdxYO/wd7A=
github.com/knadh/koanf/v2 v2.2.2/go.mod h1:abWQc0cBXLSF/PSOMCB/SK+T13NXDsPvOksbpi5e/9Q=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
Expand All @@ -489,8 +487,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/laher/mergefs v0.1.2-0.20230223191438-d16611b2f4e7 h1:PDeBswTUsSIT4QSrzLvlqKlGrANYa7TrXUwdBN9myU8=
github.com/laher/mergefs v0.1.2-0.20230223191438-d16611b2f4e7/go.mod h1:FSY1hYy94on4Tz60waRMGdO1awwS23BacqJlqf9lJ9Q=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A=
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
Expand Down Expand Up @@ -533,7 +529,6 @@ github.com/mailhog/storage v1.0.1/go.mod h1:4EAUf5xaEVd7c/OhvSxOOwQ66jT6q2er+BDB
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
Expand Down
2 changes: 2 additions & 0 deletions selfservice/strategy/oidc/provider_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type Configuration struct {
// - linkedin
// - patreon
// - amazon
// - uaepass
Provider string `json:"provider"`

// Label represents an optional label which can be used in the UI generation.
Expand Down Expand Up @@ -191,6 +192,7 @@ var supportedProviders = map[string]func(config *Configuration, reg Dependencies
"jackson": NewProviderJackson,
"fedcm-test": NewProviderTestFedcm,
"amazon": NewProviderAmazon,
"uaepass": NewProviderUAEPASS,
}

func (c ConfigurationCollection) Provider(id string, reg Dependencies) (Provider, error) {
Expand Down
1 change: 1 addition & 0 deletions selfservice/strategy/oidc/provider_private_net_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ func TestProviderPrivateIP(t *testing.T) {
// Spotify uses a fixed token URL and does not use the issuer.
// VK uses a fixed token URL and does not use the issuer.
// Yandex uses a fixed token URL and does not use the issuer.
// UAE PASS uses fixed token URL and userinfo URL and does not use the issuer value.
// NetID uses a fixed token URL and does not use the issuer.
// X uses a fixed token URL and userinfoRL and does not use the issuer value.
// Line v2.1 uses a fixed token URL and does not use the issuer.
Expand Down
179 changes: 179 additions & 0 deletions selfservice/strategy/oidc/provider_uaepass.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0

package oidc

import (
"context"
"encoding/json"
"net/url"

"github.com/hashicorp/go-retryablehttp"

"github.com/ory/x/httpx"

"github.com/pkg/errors"
"golang.org/x/oauth2"

"github.com/ory/herodot"
)

var _ OAuth2Provider = (*ProviderUAEPASS)(nil)

type ProviderUAEPASS struct {
config *Configuration
reg Dependencies
}

func NewProviderUAEPASS(config *Configuration, reg Dependencies) Provider {
return &ProviderUAEPASS{
config: config,
reg: reg,
}
}

func (p *ProviderUAEPASS) Config() *Configuration {
return p.config
}

// oauth2 returns the OAuth2 config with UAE PASS endpoints.
// Uses config.AuthURL/TokenURL if set, otherwise defaults to staging.
func (p *ProviderUAEPASS) oauth2(ctx context.Context) *oauth2.Config {
authURL := p.config.AuthURL
if authURL == "" {
authURL = "https://stg-id.uaepass.ae/idshub/authorize"
}
tokenURL := p.config.TokenURL
if tokenURL == "" {
tokenURL = "https://stg-id.uaepass.ae/idshub/token"
}

return &oauth2.Config{
ClientID: p.config.ClientID,
ClientSecret: p.config.ClientSecret,
Endpoint: oauth2.Endpoint{
AuthURL: authURL,
TokenURL: tokenURL,
AuthStyle: oauth2.AuthStyleInHeader, // client_secret_basic
},
// Use scopes from config directly — do NOT add "openid" as UAE PASS does not support it.
Scopes: p.config.Scope,
RedirectURL: p.config.Redir(p.reg.Config().OIDCRedirectURIBase(ctx)),
}
}

func (p *ProviderUAEPASS) OAuth2(ctx context.Context) (*oauth2.Config, error) {
return p.oauth2(ctx), nil
}

// AuthCodeURLOptions adds acr_values and ui_locales required/supported by UAE PASS.
func (p *ProviderUAEPASS) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption {
return []oauth2.AuthCodeOption{
oauth2.SetAuthURLParam("acr_values",
"urn:safelayer:tws:policies:authentication:level:low"),
}
}

// userinfoURL returns the userinfo endpoint. If config.IssuerURL is set,
// it derives the URL as {issuer_url}/userinfo. Otherwise, it falls back
// to the UAE PASS staging userinfo endpoint.
func (p *ProviderUAEPASS) userinfoURL() string {
if p.config.IssuerURL != "" {
return p.config.IssuerURL + "/userinfo"
}
return "https://stg-id.uaepass.ae/idshub/userinfo"
}

// Claims fetches user info from the UAE PASS userinfo endpoint and maps the
// response to Kratos Claims. All raw fields are preserved in RawClaims so
// that downstream Jsonnet mappers can access UAE PASS-specific attributes
// like userType, idn, nationalityEN, etc.
func (p *ProviderUAEPASS) Claims(ctx context.Context, exchange *oauth2.Token, query url.Values) (*Claims, error) {
o, err := p.OAuth2(ctx)
if err != nil {
return nil, err
}

ctx, client := httpx.SetOAuth2(ctx, p.reg.HTTPClient(ctx), o, exchange)
req, err := retryablehttp.NewRequestWithContext(ctx, "GET", p.userinfoURL(), nil)
if err != nil {
return nil, errors.WithStack(
herodot.ErrInternalServerError.WithWrap(err).WithReasonf("%s", err))
}

resp, err := client.Do(req)
if err != nil {
return nil, errors.WithStack(
herodot.ErrUpstreamError.WithWrap(err).WithReasonf("%s", err))
}
defer func() { _ = resp.Body.Close() }()

if err := logUpstreamError(p.reg.Logger(), resp); err != nil {
return nil, err
}

// UAE PASS userinfo response — captures all documented attributes across
// SOP1, SOP2, and SOP3 account types for both Citizens/Residents and Visitors.
// See https://docs.uaepass.ae/resources/attributes-list
var user struct {
Sub string `json:"sub"`
UUID string `json:"uuid"`
Email string `json:"email"`
Mobile string `json:"mobile"`
UserType string `json:"userType"`
FirstnameEN string `json:"firstnameEN"`
LastnameEN string `json:"lastnameEN"`
FullnameEN string `json:"fullnameEN"`
FirstnameAR string `json:"firstnameAR"`
LastnameAR string `json:"lastnameAR"`
FullnameAR string `json:"fullnameAR"`
Gender string `json:"gender"`
NationalityEN string `json:"nationalityEN"`
NationalityAR string `json:"nationalityAR"`
Idn string `json:"idn"`
IdType string `json:"idType"`
SpUUID string `json:"spuuid"`
TitleEN string `json:"titleEN"`
TitleAR string `json:"titleAR"`
ProfileType string `json:"profileType"`
UnifiedId string `json:"unifiedId"`
}

// Decode into both the typed struct and a raw map for RawClaims.
var rawClaims map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&rawClaims); err != nil {
return nil, errors.WithStack(
herodot.ErrUpstreamError.WithWrap(err).WithReasonf("%s", err))
}

// Re-marshal and unmarshal into the typed struct. This is the standard
// pattern used by other Kratos providers (e.g., NetID, generic) for
// populating both typed fields and RawClaims.
raw, err := json.Marshal(rawClaims)
if err != nil {
return nil, errors.WithStack(
herodot.ErrUpstreamError.WithWrap(err).WithReasonf("%s", err))
}
if err := json.Unmarshal(raw, &user); err != nil {
return nil, errors.WithStack(
herodot.ErrUpstreamError.WithWrap(err).WithReasonf("%s", err))
}

// Prefer UUID as the stable subject identifier; fall back to sub.
subject := user.UUID
if subject == "" {
subject = user.Sub
}

return &Claims{
Issuer: "https://id.uaepass.ae",
Subject: subject,
GivenName: user.FirstnameEN,
FamilyName: user.LastnameEN,
Name: user.FullnameEN,
Email: user.Email,
Gender: user.Gender,
PhoneNumber: user.Mobile,
RawClaims: rawClaims,
}, nil
}
55 changes: 55 additions & 0 deletions selfservice/strategy/oidc/provider_userinfo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,61 @@ func TestProviderClaimsRespectsErrorCodes(t *testing.T) {
Email: "john.doe@example.com",
},
},
{
name: "uaepass",
userInfoEndpoint: "https://stg-id.uaepass.ae/idshub/userinfo",
provider: oidc.NewProviderUAEPASS(&oidc.Configuration{
IssuerURL: "https://stg-id.uaepass.ae/idshub",
ID: "uaepass",
Provider: "uaepass",
ClientID: "sandbox_stage",
}, reg),
useToken: token,
userInfoHandler: func(req *http.Request) (*http.Response, error) {
if head := req.Header.Get("Authorization"); len(head) == 0 {
resp, err := httpmock.NewJsonResponse(401, map[string]interface{}{"error": ""})
return resp, err
}

resp, err := httpmock.NewJsonResponse(200, map[string]interface{}{
"uuid": "abc-123-uuid",
"sub": "fallback-sub",
"email": "john.doe@example.com",
"mobile": "971555555555",
"userType": "SOP3",
"firstnameEN": "John",
"lastnameEN": "Doe",
"fullnameEN": "John Doe",
"gender": "Male",
"nationalityEN": "United Arab Emirates",
"idn": "784-1234-5678901-1",
})
return resp, err
},
expectedClaims: &oidc.Claims{
Issuer: "https://id.uaepass.ae",
Subject: "abc-123-uuid",
GivenName: "John",
FamilyName: "Doe",
Name: "John Doe",
Email: "john.doe@example.com",
Gender: "Male",
PhoneNumber: "971555555555",
RawClaims: map[string]interface{}{
"uuid": "abc-123-uuid",
"sub": "fallback-sub",
"email": "john.doe@example.com",
"mobile": "971555555555",
"userType": "SOP3",
"firstnameEN": "John",
"lastnameEN": "Doe",
"fullnameEN": "John Doe",
"gender": "Male",
"nationalityEN": "United Arab Emirates",
"idn": "784-1234-5678901-1",
},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
token := token
Expand Down
Loading