mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-14 00:07:41 +00:00
chore: add saml idp for acceptance tests
This commit is contained in:
20
acceptance/idp/saml/docker-compose.yaml
Normal file
20
acceptance/idp/saml/docker-compose.yaml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
services:
|
||||||
|
samlidp:
|
||||||
|
image: golang:1.24-alpine
|
||||||
|
container_name: samlidp
|
||||||
|
command: go run main.go
|
||||||
|
environment:
|
||||||
|
API_URL: 'http://localhost:8080'
|
||||||
|
API_DOMAIN: 'localhost:8080'
|
||||||
|
PAT_FILE: '/pat/zitadel-admin-sa.pat'
|
||||||
|
SCHEMA: 'http'
|
||||||
|
HOST: 'localhost'
|
||||||
|
PORT: "8003"
|
||||||
|
working_dir: /saml
|
||||||
|
ports:
|
||||||
|
- 8003:8003
|
||||||
|
volumes:
|
||||||
|
- "../../pat:/pat"
|
||||||
|
- "./:/saml"
|
||||||
|
extra_hosts:
|
||||||
|
- "localhost:host-gateway"
|
16
acceptance/idp/saml/go.mod
Normal file
16
acceptance/idp/saml/go.mod
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
module github.com/zitadel/typescript/acceptance/idp/saml
|
||||||
|
|
||||||
|
go 1.24.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/crewjam/saml v0.4.14
|
||||||
|
github.com/mattermost/xml-roundtrip-validator v0.1.0
|
||||||
|
github.com/zenazn/goji v1.0.1
|
||||||
|
golang.org/x/crypto v0.36.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/beevik/etree v1.1.0 // indirect
|
||||||
|
github.com/jonboulle/clockwork v0.2.2 // indirect
|
||||||
|
github.com/russellhaering/goxmldsig v1.3.0 // indirect
|
||||||
|
)
|
49
acceptance/idp/saml/go.sum
Normal file
49
acceptance/idp/saml/go.sum
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
||||||
|
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c=
|
||||||
|
github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
|
||||||
|
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU=
|
||||||
|
github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
|
github.com/russellhaering/goxmldsig v1.3.0 h1:DllIWUgMy0cRUMfGiASiYEa35nsieyD3cigIwLonTPM=
|
||||||
|
github.com/russellhaering/goxmldsig v1.3.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8=
|
||||||
|
github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||||
|
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||||
|
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||||
|
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
330
acceptance/idp/saml/main.go
Normal file
330
acceptance/idp/saml/main.go
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/crewjam/saml"
|
||||||
|
"github.com/crewjam/saml/logger"
|
||||||
|
"github.com/crewjam/saml/samlidp"
|
||||||
|
xrv "github.com/mattermost/xml-roundtrip-validator"
|
||||||
|
"github.com/zenazn/goji"
|
||||||
|
"github.com/zenazn/goji/bind"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var key = func() crypto.PrivateKey {
|
||||||
|
b, _ := pem.Decode([]byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpAIBAAKCAQEA0OhbMuizgtbFOfwbK7aURuXhZx6VRuAs3nNibiuifwCGz6u9
|
||||||
|
yy7bOR0P+zqN0YkjxaokqFgra7rXKCdeABmoLqCC0U+cGmLNwPOOA0PaD5q5xKhQ
|
||||||
|
4Me3rt/R9C4Ca6k3/OnkxnKwnogcsmdgs2l8liT3qVHP04Oc7Uymq2v09bGb6nPu
|
||||||
|
fOrkXS9F6mSClxHG/q59AGOWsXK1xzIRV1eu8W2SNdyeFVU1JHiQe444xLoPul5t
|
||||||
|
InWasKayFsPlJfWNc8EoU8COjNhfo/GovFTHVjh9oUR/gwEFVwifIHihRE0Hazn2
|
||||||
|
EQSLaOr2LM0TsRsQroFjmwSGgI+X2bfbMTqWOQIDAQABAoIBAFWZwDTeESBdrLcT
|
||||||
|
zHZe++cJLxE4AObn2LrWANEv5AeySYsyzjRBYObIN9IzrgTb8uJ900N/zVr5VkxH
|
||||||
|
xUa5PKbOcowd2NMfBTw5EEnaNbILLm+coHdanrNzVu59I9TFpAFoPavrNt/e2hNo
|
||||||
|
NMGPSdOkFi81LLl4xoadz/WR6O/7N2famM+0u7C2uBe+TrVwHyuqboYoidJDhO8M
|
||||||
|
w4WlY9QgAUhkPyzZqrl+VfF1aDTGVf4LJgaVevfFCas8Ws6DQX5q4QdIoV6/0vXi
|
||||||
|
B1M+aTnWjHuiIzjBMWhcYW2+I5zfwNWRXaxdlrYXRukGSdnyO+DH/FhHePJgmlkj
|
||||||
|
NInADDkCgYEA6MEQFOFSCc/ELXYWgStsrtIlJUcsLdLBsy1ocyQa2lkVUw58TouW
|
||||||
|
RciE6TjW9rp31pfQUnO2l6zOUC6LT9Jvlb9PSsyW+rvjtKB5PjJI6W0hjX41wEO6
|
||||||
|
fshFELMJd9W+Ezao2AsP2hZJ8McCF8no9e00+G4xTAyxHsNI2AFTCQcCgYEA5cWZ
|
||||||
|
JwNb4t7YeEajPt9xuYNUOQpjvQn1aGOV7KcwTx5ELP/Hzi723BxHs7GSdrLkkDmi
|
||||||
|
Gpb+mfL4wxCt0fK0i8GFQsRn5eusyq9hLqP/bmjpHoXe/1uajFbE1fZQR+2LX05N
|
||||||
|
3ATlKaH2hdfCJedFa4wf43+cl6Yhp6ZA0Yet1r8CgYEAwiu1j8W9G+RRA5/8/DtO
|
||||||
|
yrUTOfsbFws4fpLGDTA0mq0whf6Soy/96C90+d9qLaC3srUpnG9eB0CpSOjbXXbv
|
||||||
|
kdxseLkexwOR3bD2FHX8r4dUM2bzznZyEaxfOaQypN8SV5ME3l60Fbr8ajqLO288
|
||||||
|
wlTmGM5Mn+YCqOg/T7wjGmcCgYBpzNfdl/VafOROVbBbhgXWtzsz3K3aYNiIjbp+
|
||||||
|
MunStIwN8GUvcn6nEbqOaoiXcX4/TtpuxfJMLw4OvAJdtxUdeSmEee2heCijV6g3
|
||||||
|
ErrOOy6EqH3rNWHvlxChuP50cFQJuYOueO6QggyCyruSOnDDuc0BM0SGq6+5g5s7
|
||||||
|
H++S/wKBgQDIkqBtFr9UEf8d6JpkxS0RXDlhSMjkXmkQeKGFzdoJcYVFIwq8jTNB
|
||||||
|
nJrVIGs3GcBkqGic+i7rTO1YPkquv4dUuiIn+vKZVoO6b54f+oPBXd4S0BnuEqFE
|
||||||
|
rdKNuCZhiaE2XD9L/O9KP1fh5bfEcKwazQ23EvpJHBMm8BGC+/YZNw==
|
||||||
|
-----END RSA PRIVATE KEY-----`))
|
||||||
|
k, _ := x509.ParsePKCS1PrivateKey(b.Bytes)
|
||||||
|
return k
|
||||||
|
}()
|
||||||
|
|
||||||
|
var cert = func() *x509.Certificate {
|
||||||
|
b, _ := pem.Decode([]byte(`-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNV
|
||||||
|
BAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5
|
||||||
|
NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB
|
||||||
|
BQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8A
|
||||||
|
hs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+a
|
||||||
|
ucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWx
|
||||||
|
m+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6
|
||||||
|
D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURN
|
||||||
|
B2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0O
|
||||||
|
BBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56
|
||||||
|
zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5
|
||||||
|
pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uv
|
||||||
|
NONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEf
|
||||||
|
y/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL
|
||||||
|
/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsb
|
||||||
|
GFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTL
|
||||||
|
UzreO96WzlBBMtY=
|
||||||
|
-----END CERTIFICATE-----`))
|
||||||
|
c, _ := x509.ParseCertificate(b.Bytes)
|
||||||
|
return c
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Example from https://github.com/crewjam/saml/blob/main/example/idp/idp.go
|
||||||
|
func main() {
|
||||||
|
apiURL := os.Getenv("API_URL")
|
||||||
|
patFile := os.Getenv("PAT_FILE")
|
||||||
|
domain := os.Getenv("API_DOMAIN")
|
||||||
|
schema := os.Getenv("SCHEMA")
|
||||||
|
host := os.Getenv("HOST")
|
||||||
|
port := os.Getenv("PORT")
|
||||||
|
|
||||||
|
f, err := os.Open(patFile)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
pat, err := io.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
patStr := strings.Trim(string(pat), "\n")
|
||||||
|
|
||||||
|
baseURL, err := url.Parse(schema + "://" + host + ":" + port)
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
idpServer, err := samlidp.New(samlidp.Options{
|
||||||
|
URL: *baseURL,
|
||||||
|
Logger: logger.DefaultLogger,
|
||||||
|
Key: key,
|
||||||
|
Certificate: cert,
|
||||||
|
Store: &samlidp.MemoryStore{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata, err := xml.MarshalIndent(idpServer.IDP.Metadata(), "", " ")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
idpID, err := createZitadelResources(apiURL, patStr, domain, metadata)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
lis := bind.Socket(":" + baseURL.Port())
|
||||||
|
goji.Handle("/*", idpServer)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
goji.ServeListener(lis)
|
||||||
|
}()
|
||||||
|
|
||||||
|
addService(idpServer, apiURL+"/idps/"+idpID+"/saml/metadata")
|
||||||
|
addUsers(idpServer)
|
||||||
|
|
||||||
|
sigChan := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
<-sigChan
|
||||||
|
|
||||||
|
if err := lis.Close(); err != nil {
|
||||||
|
log.Fatalf("HTTP shutdown error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addService(idpServer *samlidp.Server, spURLStr string) {
|
||||||
|
metadataResp, err := http.Get(spURLStr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer metadataResp.Body.Close()
|
||||||
|
spMetadata, err := getSPMetadata(metadataResp.Body)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = idpServer.Store.Put("/services/sp", samlidp.Service{
|
||||||
|
Name: spURLStr,
|
||||||
|
Metadata: *spMetadata,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSPMetadata(r io.Reader) (spMetadata *saml.EntityDescriptor, err error) {
|
||||||
|
var data []byte
|
||||||
|
if data, err = io.ReadAll(r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
spMetadata = &saml.EntityDescriptor{}
|
||||||
|
if err := xrv.Validate(bytes.NewBuffer(data)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := xml.Unmarshal(data, &spMetadata); err != nil {
|
||||||
|
if err.Error() == "expected element type <EntityDescriptor> but have <EntitiesDescriptor>" {
|
||||||
|
entities := &saml.EntitiesDescriptor{}
|
||||||
|
if err := xml.Unmarshal(data, &entities); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range entities.EntityDescriptors {
|
||||||
|
if len(e.SPSSODescriptors) > 0 {
|
||||||
|
return &e, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// there were no SPSSODescriptors in the response
|
||||||
|
return nil, errors.New("metadata contained no service provider metadata")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return spMetadata, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addUsers(idpServer *samlidp.Server) {
|
||||||
|
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("hunter2"), bcrypt.DefaultCost)
|
||||||
|
err := idpServer.Store.Put("/users/alice", samlidp.User{Name: "alice",
|
||||||
|
HashedPassword: hashedPassword,
|
||||||
|
Groups: []string{"Administrators", "Users"},
|
||||||
|
Email: "alice@example.com",
|
||||||
|
CommonName: "Alice Smith",
|
||||||
|
Surname: "Smith",
|
||||||
|
GivenName: "Alice",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = idpServer.Store.Put("/users/bob", samlidp.User{
|
||||||
|
Name: "bob",
|
||||||
|
HashedPassword: hashedPassword,
|
||||||
|
Groups: []string{"Users"},
|
||||||
|
Email: "bob@example.com",
|
||||||
|
CommonName: "Bob Smith",
|
||||||
|
Surname: "Smith",
|
||||||
|
GivenName: "Bob",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createZitadelResources(apiURL, pat, domain string, metadata []byte) (string, error) {
|
||||||
|
idpID, err := CreateIDP(apiURL, pat, domain, metadata)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return idpID, ActivateIDP(apiURL, pat, domain, idpID)
|
||||||
|
}
|
||||||
|
|
||||||
|
type createIDP struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
MetadataXml string `json:"metadataXml"`
|
||||||
|
Binding string `json:"binding"`
|
||||||
|
WithSignedRequest bool `json:"withSignedRequest"`
|
||||||
|
ProviderOptions providerOptions `json:"providerOptions"`
|
||||||
|
NameIdFormat string `json:"nameIdFormat"`
|
||||||
|
}
|
||||||
|
type providerOptions struct {
|
||||||
|
IsLinkingAllowed bool `json:"isLinkingAllowed"`
|
||||||
|
IsCreationAllowed bool `json:"isCreationAllowed"`
|
||||||
|
IsAutoCreation bool `json:"isAutoCreation"`
|
||||||
|
IsAutoUpdate bool `json:"isAutoUpdate"`
|
||||||
|
AutoLinking string `json:"autoLinking"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type idp struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateIDP(apiURL, pat, domain string, idpMetadata []byte) (string, error) {
|
||||||
|
encoded := make([]byte, base64.URLEncoding.EncodedLen(len(idpMetadata)))
|
||||||
|
base64.URLEncoding.Encode(encoded, idpMetadata)
|
||||||
|
|
||||||
|
createIDP := &createIDP{
|
||||||
|
Name: "CREWJAM",
|
||||||
|
MetadataXml: string(encoded),
|
||||||
|
Binding: "SAML_BINDING_POST",
|
||||||
|
WithSignedRequest: true,
|
||||||
|
ProviderOptions: providerOptions{
|
||||||
|
IsLinkingAllowed: true,
|
||||||
|
IsCreationAllowed: true,
|
||||||
|
IsAutoCreation: true,
|
||||||
|
IsAutoUpdate: true,
|
||||||
|
AutoLinking: "AUTO_LINKING_OPTION_USERNAME",
|
||||||
|
},
|
||||||
|
NameIdFormat: "SAML_NAME_ID_FORMAT_PERSISTENT",
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := doRequestWithHeaders(apiURL+"/admin/v1/idps/saml", pat, domain, createIDP)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
idp := new(idp)
|
||||||
|
if err := json.Unmarshal(data, idp); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return idp.ID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type activateIDP struct {
|
||||||
|
IdpId string `json:"idpId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ActivateIDP(apiURL, pat, domain string, idpID string) error {
|
||||||
|
activateIDP := &activateIDP{
|
||||||
|
IdpId: idpID,
|
||||||
|
}
|
||||||
|
_, err := doRequestWithHeaders(apiURL+"/admin/v1/policies/login/idps", pat, domain, activateIDP)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func doRequestWithHeaders(apiURL, pat, domain string, body any) (*http.Response, error) {
|
||||||
|
data, err := json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, apiURL, io.NopCloser(bytes.NewReader(data)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
values := http.Header{}
|
||||||
|
values.Add("Authorization", "Bearer "+pat)
|
||||||
|
values.Add("x-forwarded-host", domain)
|
||||||
|
values.Add("Content-Type", "application/json")
|
||||||
|
req.Header = values
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
@@ -4,7 +4,6 @@ go 1.24.1
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/joho/godotenv v1.5.1
|
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/zitadel/logging v0.6.1
|
github.com/zitadel/logging v0.6.1
|
||||||
github.com/zitadel/oidc/v3 v3.36.1
|
github.com/zitadel/oidc/v3 v3.36.1
|
||||||
|
@@ -22,8 +22,6 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX
|
|||||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||||
github.com/jeremija/gosubmit v0.2.8 h1:mmSITBz9JxVtu8eqbN+zmmwX7Ij2RidQxhcwRVI4wqA=
|
github.com/jeremija/gosubmit v0.2.8 h1:mmSITBz9JxVtu8eqbN+zmmwX7Ij2RidQxhcwRVI4wqA=
|
||||||
github.com/jeremija/gosubmit v0.2.8/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI=
|
github.com/jeremija/gosubmit v0.2.8/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
|
||||||
github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM=
|
github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM=
|
||||||
github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM=
|
github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM=
|
||||||
github.com/muhlemmer/httpforwarded v0.1.0 h1:x4DLrzXdliq8mprgUMR0olDvHGkou5BJsK/vWUetyzY=
|
github.com/muhlemmer/httpforwarded v0.1.0 h1:x4DLrzXdliq8mprgUMR0olDvHGkou5BJsK/vWUetyzY=
|
||||||
|
@@ -20,7 +20,6 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
|
||||||
"github.com/zitadel/logging"
|
"github.com/zitadel/logging"
|
||||||
"github.com/zitadel/oidc/v3/pkg/client/rp"
|
"github.com/zitadel/oidc/v3/pkg/client/rp"
|
||||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||||
@@ -33,11 +32,6 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
err := godotenv.Load()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error loading .env file")
|
|
||||||
}
|
|
||||||
|
|
||||||
apiURL := os.Getenv("API_URL")
|
apiURL := os.Getenv("API_URL")
|
||||||
patFile := os.Getenv("PAT_FILE")
|
patFile := os.Getenv("PAT_FILE")
|
||||||
domain := os.Getenv("API_DOMAIN")
|
domain := os.Getenv("API_DOMAIN")
|
||||||
@@ -206,12 +200,7 @@ func CreateProject(apiURL, pat, domain string) (string, error) {
|
|||||||
HasProjectCheck: false,
|
HasProjectCheck: false,
|
||||||
PrivateLabelingSetting: "PRIVATE_LABELING_SETTING_UNSPECIFIED",
|
PrivateLabelingSetting: "PRIVATE_LABELING_SETTING_UNSPECIFIED",
|
||||||
}
|
}
|
||||||
body, err := json.Marshal(createProject)
|
resp, err := doRequestWithHeaders(apiURL+"/management/v1/projects", pat, domain, createProject)
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := doRequestWithHeaders(apiURL+"/management/v1/projects", pat, domain, body)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -284,12 +273,8 @@ func CreateApp(apiURL, pat, domain, projectID string, redirectURI, loginURL stri
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
body, err := json.Marshal(createApp)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := doRequestWithHeaders(apiURL+"/management/v1/projects/"+projectID+"/apps/oidc", pat, domain, body)
|
resp, err := doRequestWithHeaders(apiURL+"/management/v1/projects/"+projectID+"/apps/oidc", pat, domain, createApp)
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
@@ -303,8 +288,12 @@ func CreateApp(apiURL, pat, domain, projectID string, redirectURI, loginURL stri
|
|||||||
return a.ClientID, a.ClientSecret, err
|
return a.ClientID, a.ClientSecret, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func doRequestWithHeaders(apiURL, pat, domain string, body []byte) (*http.Response, error) {
|
func doRequestWithHeaders(apiURL, pat, domain string, body any) (*http.Response, error) {
|
||||||
req, err := http.NewRequest(http.MethodPost, apiURL, io.NopCloser(bytes.NewReader(body)))
|
data, err := json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(http.MethodPost, apiURL, io.NopCloser(bytes.NewReader(data)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -194,12 +194,7 @@ func CreateProject(apiURL, pat, domain string) (string, error) {
|
|||||||
HasProjectCheck: false,
|
HasProjectCheck: false,
|
||||||
PrivateLabelingSetting: "PRIVATE_LABELING_SETTING_UNSPECIFIED",
|
PrivateLabelingSetting: "PRIVATE_LABELING_SETTING_UNSPECIFIED",
|
||||||
}
|
}
|
||||||
body, err := json.Marshal(createProject)
|
resp, err := doRequestWithHeaders(apiURL+"/management/v1/projects", pat, domain, createProject)
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := doRequestWithHeaders(apiURL+"/management/v1/projects", pat, domain, body)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -241,17 +236,17 @@ func CreateApp(apiURL, pat, domain, projectID string, spMetadata []byte, loginUR
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
body, err := json.Marshal(createApp)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = doRequestWithHeaders(apiURL+"/management/v1/projects/"+projectID+"/apps/saml", pat, domain, body)
|
_, err := doRequestWithHeaders(apiURL+"/management/v1/projects/"+projectID+"/apps/saml", pat, domain, createApp)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func doRequestWithHeaders(apiURL, pat, domain string, body []byte) (*http.Response, error) {
|
func doRequestWithHeaders(apiURL, pat, domain string, body any) (*http.Response, error) {
|
||||||
req, err := http.NewRequest(http.MethodPost, apiURL, io.NopCloser(bytes.NewReader(body)))
|
data, err := json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(http.MethodPost, apiURL, io.NopCloser(bytes.NewReader(data)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -27,6 +27,7 @@
|
|||||||
"run-zitadel": "docker compose -f ./acceptance/docker-compose.yaml run setup",
|
"run-zitadel": "docker compose -f ./acceptance/docker-compose.yaml run setup",
|
||||||
"run-sink": "docker compose -f ./acceptance/docker-compose.yaml up -d sink",
|
"run-sink": "docker compose -f ./acceptance/docker-compose.yaml up -d sink",
|
||||||
"run-samlsp": "docker compose -f ./acceptance/saml/docker-compose.yaml up -d",
|
"run-samlsp": "docker compose -f ./acceptance/saml/docker-compose.yaml up -d",
|
||||||
|
"run-samlidp": "docker compose -f ./acceptance/idp/saml/docker-compose.yaml up -d",
|
||||||
"run-oidcrp": "docker compose -f ./acceptance/oidc/docker-compose.yaml up -d",
|
"run-oidcrp": "docker compose -f ./acceptance/oidc/docker-compose.yaml up -d",
|
||||||
"stop": "docker compose -f ./acceptance/docker-compose.yaml stop"
|
"stop": "docker compose -f ./acceptance/docker-compose.yaml stop"
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user