mirror of
https://github.com/paralus/paralus.git
synced 2026-08-24 15:47:19 +00:00
Merge pull request #10 from RafaySystems/oidc-init
Support for OIdC authentiation
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
module github.com/RafaySystems/rcloud-base/components/usermgmt/_kratos
|
||||
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/uptrace/bun v1.0.21
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.0.21
|
||||
github.com/uptrace/bun/driver/pgdriver v1.0.21
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
|
||||
mellium.im/sasl v0.2.1 // indirect
|
||||
)
|
||||
@@ -0,0 +1,46 @@
|
||||
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/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
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/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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
github.com/uptrace/bun v1.0.21 h1:5ek4bnrEmZo6wvY/RHt1dJNXzOPOnrfJeZMoZfZt9Io=
|
||||
github.com/uptrace/bun v1.0.21/go.mod h1:u+QsgCgjGFwshy3euGAN1CLEO9RMf42lga5jQ/ezYsc=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.0.21 h1:2MoMW1qPpAQkwjNDBmQaZBAR07qpijJsw/p6gYzwdl0=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.0.21/go.mod h1:r6JgWFNXFqSqHy+Qxop0NJzHvaZQDEyz2fJBICNsv+I=
|
||||
github.com/uptrace/bun/driver/pgdriver v1.0.21 h1:A6X0aGGkFguVJThhfcq6bZlPU1KAyXRmI9Tsf33s2QE=
|
||||
github.com/uptrace/bun/driver/pgdriver v1.0.21/go.mod h1:sV53gIuC3GsW/SVjobgMfyQmvhB4CgGQrpiYayuKjVs=
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211123173158-ef496fb156ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
mellium.im/sasl v0.2.1 h1:nspKSRg7/SyO0cRGY71OkfHab8tf9kCts6a6oTDut0w=
|
||||
mellium.im/sasl v0.2.1/go.mod h1:ROaEDLQNuf9vjKqE1SrAfnsobm2YKXT1gnN1uDp1PjQ=
|
||||
@@ -18,6 +18,8 @@ selfservice:
|
||||
methods:
|
||||
password:
|
||||
enabled: true
|
||||
oidc:
|
||||
enabled: true
|
||||
|
||||
flows:
|
||||
error:
|
||||
@@ -51,8 +53,10 @@ selfservice:
|
||||
after:
|
||||
password:
|
||||
hooks:
|
||||
-
|
||||
hook: session
|
||||
- hook: session
|
||||
oidc:
|
||||
hooks:
|
||||
- hook: session
|
||||
|
||||
log:
|
||||
level: debug
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
selfservice:
|
||||
methods:
|
||||
oidc:
|
||||
config:
|
||||
providers:
|
||||
# This is an example provider
|
||||
- id: uuid
|
||||
provider: generic
|
||||
mapper_url: http://mydomain.com/github.schema.json
|
||||
client_id: ...
|
||||
client_secret: ...
|
||||
scope:
|
||||
- email
|
||||
issuer_url: http://openid-connect-provider/
|
||||
auth_url: http://openid-connect-provider/oauth2/auth
|
||||
token_url: http://openid-connect-provider/oauth2/token
|
||||
requested_claims:
|
||||
userinfo:
|
||||
given_name:
|
||||
essential: true
|
||||
nickname: null
|
||||
email:
|
||||
essential: true
|
||||
email_verified:
|
||||
essential: true
|
||||
picture: null
|
||||
http://example/info/claims/groups: null
|
||||
id_token:
|
||||
auth_time:
|
||||
essential: true
|
||||
acr:
|
||||
values: ['urn:mace:incommon:iap:silver']
|
||||
sub:
|
||||
value: 248289761001
|
||||
@@ -0,0 +1,83 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect/pgdialect"
|
||||
"github.com/uptrace/bun/driver/pgdriver"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type Provider struct {
|
||||
Id uuid.UUID `bun:"id,type:uuid"`
|
||||
Provider string `bun:"provider_name,notnull"`
|
||||
MapperURL string `bun:"mapper_url" yaml:"mapper_url"`
|
||||
ClientId string `bun:"client_id,notnull" yaml:"client_id"`
|
||||
ClientSecret string `bun:"client_secret,notnull" yaml:"client_secret"`
|
||||
Scope []string `bun:"scopes,notnull"`
|
||||
IssuerURL string `bun:"issuer_url,notnull" yaml:"issuer_url"`
|
||||
AuthURL string `bun:"auth_url" yaml:"auth_url,omitempty"`
|
||||
TokenURL string `bun:"token_url" yaml:"token_url,omitempty"`
|
||||
RequestedClaims map[string]interface{} `bun:"type:jsonb" yaml:"requested_claims,omitempty"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Selfservice struct {
|
||||
Methods struct {
|
||||
Oidc struct {
|
||||
Config struct {
|
||||
Providers []Provider
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var ProvidersDB []Provider
|
||||
|
||||
func sync(ctx context.Context, db *bun.DB) error {
|
||||
err := db.NewSelect().Model(&ProvidersDB).ModelTableExpr("authsrv_oidc_provider AS provider").Scan(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch providers from DB: %s", err)
|
||||
}
|
||||
|
||||
var c Config
|
||||
c.Selfservice.Methods.Oidc.Config.Providers = ProvidersDB
|
||||
d, err := yaml.Marshal(&c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal: %s", err)
|
||||
}
|
||||
err = os.WriteFile("oidc_providers.yml", d, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write data: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
dsn := "postgres://admindbuser:admindbpassword@localhost:5432/admindb?sslmode=disable"
|
||||
sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn)))
|
||||
db := bun.NewDB(sqldb, pgdialect.New())
|
||||
|
||||
ln := pgdriver.NewListener(db)
|
||||
if err := ln.Listen(ctx, "provider:changed"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for range ln.Channel() {
|
||||
fmt.Printf("%s: Received notification\n", time.Now())
|
||||
if err := sync(ctx, db); err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
fmt.Println("Synchronized successfully")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,875 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "OIdC Provider Service",
|
||||
"version": "2.0",
|
||||
"contact": {
|
||||
"name": "Rafay Dev"
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "OIDCProvider"
|
||||
}
|
||||
],
|
||||
"schemes": [
|
||||
"https"
|
||||
],
|
||||
"consumes": [
|
||||
"application/json",
|
||||
"application/yaml"
|
||||
],
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/yaml"
|
||||
],
|
||||
"paths": {
|
||||
"/auth/v3/sso/oidc/provider": {
|
||||
"get": {
|
||||
"operationId": "OIDCProvider_ListOIDCProvider",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v3OIDCProviderList"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Returned when the user does not have permission to access the resource.",
|
||||
"schema": {}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/googlerpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"OIDCProvider"
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"operationId": "OIDCProvider_CreateOIDCProvider",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v3OIDCProvider"
|
||||
}
|
||||
},
|
||||
"201": {
|
||||
"description": "Returned when oidc provider is created successfully.",
|
||||
"schema": {}
|
||||
},
|
||||
"403": {
|
||||
"description": "Returned when the user does not have permission to access the resource.",
|
||||
"schema": {}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/googlerpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v3OIDCProvider"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"OIDCProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/auth/v3/sso/oidc/provider/{metadata.id}": {
|
||||
"get": {
|
||||
"operationId": "OIDCProvider_GetOIDCProvider",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v3OIDCProvider"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Returned when the user does not have permission to access the resource.",
|
||||
"schema": {}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/googlerpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "metadata.id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "apiVersion",
|
||||
"description": "API Version. API Version of the OIDCProvider resource",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"default": "usermgmt.k8smgmt.io/v3"
|
||||
},
|
||||
{
|
||||
"name": "kind",
|
||||
"description": "Kind. Kind of the OIDCProvider resource",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"default": "OIDCProvider"
|
||||
},
|
||||
{
|
||||
"name": "metadata.name",
|
||||
"description": "Name. name of the resource",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "metadata.description",
|
||||
"description": "Description. description of the resource",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "metadata.project",
|
||||
"description": "Project. Project of the resource",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "metadata.organization",
|
||||
"description": "Organization. Organization to which the resource belongs",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "metadata.partner",
|
||||
"description": "Partner. Partner to which the resource belongs",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "metadata.modifiedAt",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
{
|
||||
"name": "spec.providerName",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "spec.mapperUrl",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "spec.mapperFilename",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "spec.clientId",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "spec.clientSecret",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "spec.scopes",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectionFormat": "multi"
|
||||
},
|
||||
{
|
||||
"name": "spec.issuerUrl",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "spec.authUrl",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "spec.tokenUrl",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "spec.predefined",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "spec.callbackUrl",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "status.conditionType",
|
||||
"description": "Condition Type. type of the status condition",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "status.conditionStatus",
|
||||
"description": "Condition Status. status of the condition",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"StatusNotSet",
|
||||
"StatusSubmitted",
|
||||
"StatusOK",
|
||||
"StatusFailed"
|
||||
],
|
||||
"default": "StatusNotSet"
|
||||
},
|
||||
{
|
||||
"name": "status.lastUpdated",
|
||||
"description": "Last Updated. when the condition status is last updated",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
{
|
||||
"name": "status.reason",
|
||||
"description": "Reason. reason of the last condition status",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"OIDCProvider"
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"operationId": "OIDCProvider_DeleteOIDCProvider",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"properties": {}
|
||||
}
|
||||
},
|
||||
"204": {
|
||||
"description": "Returned when oidc provider is deleted successfully.",
|
||||
"schema": {}
|
||||
},
|
||||
"403": {
|
||||
"description": "Returned when the user does not have permission to access the resource.",
|
||||
"schema": {}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/googlerpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "metadata.id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "apiVersion",
|
||||
"description": "API Version. API Version of the OIDCProvider resource",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"default": "usermgmt.k8smgmt.io/v3"
|
||||
},
|
||||
{
|
||||
"name": "kind",
|
||||
"description": "Kind. Kind of the OIDCProvider resource",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"default": "OIDCProvider"
|
||||
},
|
||||
{
|
||||
"name": "metadata.name",
|
||||
"description": "Name. name of the resource",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "metadata.description",
|
||||
"description": "Description. description of the resource",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "metadata.project",
|
||||
"description": "Project. Project of the resource",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "metadata.organization",
|
||||
"description": "Organization. Organization to which the resource belongs",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "metadata.partner",
|
||||
"description": "Partner. Partner to which the resource belongs",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "metadata.modifiedAt",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
{
|
||||
"name": "spec.providerName",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "spec.mapperUrl",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "spec.mapperFilename",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "spec.clientId",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "spec.clientSecret",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "spec.scopes",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectionFormat": "multi"
|
||||
},
|
||||
{
|
||||
"name": "spec.issuerUrl",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "spec.authUrl",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "spec.tokenUrl",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "spec.predefined",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "spec.callbackUrl",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "status.conditionType",
|
||||
"description": "Condition Type. type of the status condition",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "status.conditionStatus",
|
||||
"description": "Condition Status. status of the condition",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"StatusNotSet",
|
||||
"StatusSubmitted",
|
||||
"StatusOK",
|
||||
"StatusFailed"
|
||||
],
|
||||
"default": "StatusNotSet"
|
||||
},
|
||||
{
|
||||
"name": "status.lastUpdated",
|
||||
"description": "Last Updated. when the condition status is last updated",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
{
|
||||
"name": "status.reason",
|
||||
"description": "Reason. reason of the last condition status",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"OIDCProvider"
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"operationId": "OIDCProvider_UpdateOIDCProvider",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v3OIDCProvider"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Returned when the user does not have permission to access the resource.",
|
||||
"schema": {}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/googlerpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "metadata.id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v3OIDCProvider"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"OIDCProvider"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"commonv3Status": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"conditionType": {
|
||||
"type": "string",
|
||||
"description": "type of the status condition",
|
||||
"title": "Condition Type",
|
||||
"readOnly": true
|
||||
},
|
||||
"conditionStatus": {
|
||||
"$ref": "#/definitions/v3ConditionStatus",
|
||||
"enum": [
|
||||
"StatusNotSet",
|
||||
"StatusSubmitted",
|
||||
"StatusOK",
|
||||
"StatusFailed"
|
||||
],
|
||||
"description": "status of the condition",
|
||||
"title": "Condition Status",
|
||||
"readOnly": true
|
||||
},
|
||||
"lastUpdated": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "when the condition status is last updated",
|
||||
"title": "Last Updated",
|
||||
"readOnly": true
|
||||
},
|
||||
"reason": {
|
||||
"type": "string",
|
||||
"description": "reason of the last condition status",
|
||||
"title": "Reason",
|
||||
"readOnly": true
|
||||
}
|
||||
},
|
||||
"description": "status of a resource",
|
||||
"title": "Status",
|
||||
"readOnly": true
|
||||
},
|
||||
"googlerpcStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"protobufAny": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"@type": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": {}
|
||||
},
|
||||
"protobufNullValue": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"NULL_VALUE"
|
||||
],
|
||||
"default": "NULL_VALUE",
|
||||
"description": "`NullValue` is a singleton enumeration to represent the null value for the\n`Value` type union.\n\n The JSON representation for `NullValue` is JSON `null`.\n\n - NULL_VALUE: Null value."
|
||||
},
|
||||
"v3ConditionStatus": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"StatusNotSet",
|
||||
"StatusSubmitted",
|
||||
"StatusOK",
|
||||
"StatusFailed"
|
||||
],
|
||||
"default": "StatusNotSet",
|
||||
"title": "$title: ConditionStatus\n$description: status of a condition for a resource"
|
||||
},
|
||||
"v3ListMetadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"count": {
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
},
|
||||
"offset": {
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
},
|
||||
"limt": {
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
}
|
||||
},
|
||||
"title": "$title: ListMetadata\n$description: metadata for a list of resources\n$required: enabled"
|
||||
},
|
||||
"v3Metadata": {
|
||||
"type": "object",
|
||||
"example": {
|
||||
"name": "some-name",
|
||||
"project": "defaultproject"
|
||||
},
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "name of the resource",
|
||||
"title": "Name"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "description of the resource",
|
||||
"title": "Description"
|
||||
},
|
||||
"labels": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "labels of the resource",
|
||||
"title": "Lables"
|
||||
},
|
||||
"annotations": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "annotations of the resource",
|
||||
"title": "Annotations"
|
||||
},
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "Project of the resource",
|
||||
"title": "Project"
|
||||
},
|
||||
"organization": {
|
||||
"type": "string",
|
||||
"description": "Organization to which the resource belongs",
|
||||
"title": "Organization"
|
||||
},
|
||||
"partner": {
|
||||
"type": "string",
|
||||
"description": "Partner to which the resource belongs",
|
||||
"title": "Partner"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"readOnly": true
|
||||
},
|
||||
"modifiedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"readOnly": true
|
||||
}
|
||||
},
|
||||
"description": "metadata of the resource",
|
||||
"title": "Metadata",
|
||||
"required": [
|
||||
"name",
|
||||
"project"
|
||||
]
|
||||
},
|
||||
"v3OIDCProvider": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"type": "string",
|
||||
"default": "usermgmt.k8smgmt.io/v3",
|
||||
"description": "API Version of the OIDCProvider resource",
|
||||
"title": "API Version"
|
||||
},
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"default": "OIDCProvider",
|
||||
"description": "Kind of the OIDCProvider resource",
|
||||
"title": "Kind"
|
||||
},
|
||||
"metadata": {
|
||||
"$ref": "#/definitions/v3Metadata",
|
||||
"description": "Metadata of the OIDCProvider resource",
|
||||
"title": "Metadata"
|
||||
},
|
||||
"spec": {
|
||||
"$ref": "#/definitions/v3OIDCProviderSpec",
|
||||
"description": "Spec of the OIDCProvider resource",
|
||||
"title": "Spec"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/commonv3Status",
|
||||
"description": "Status of the resource",
|
||||
"title": "Status",
|
||||
"readOnly": true
|
||||
}
|
||||
},
|
||||
"description": "Open Id Connect Provider",
|
||||
"title": "OIDCProvider",
|
||||
"required": [
|
||||
"apiVersion",
|
||||
"kind",
|
||||
"metadata",
|
||||
"spec"
|
||||
]
|
||||
},
|
||||
"v3OIDCProviderList": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"type": "string",
|
||||
"default": "usermgmt.k8smgmt.io/v3",
|
||||
"description": "API Version of the OIDCProvider list resource",
|
||||
"title": "API Version",
|
||||
"readOnly": true
|
||||
},
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"default": "OIDCProviderList",
|
||||
"description": "Kind of the OIDCProvider list resource",
|
||||
"title": "Kind",
|
||||
"readOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"$ref": "#/definitions/v3ListMetadata",
|
||||
"description": "Metadata of the OIDCProvider list resource",
|
||||
"title": "Metadata",
|
||||
"readOnly": true
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/v3OIDCProvider"
|
||||
},
|
||||
"description": "List of the OIDCProvider resources",
|
||||
"title": "Items",
|
||||
"readOnly": true
|
||||
}
|
||||
},
|
||||
"description": "OIDC providers list",
|
||||
"title": "OIDCProviderList",
|
||||
"readOnly": true
|
||||
},
|
||||
"v3OIDCProviderSpec": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"providerName": {
|
||||
"type": "string"
|
||||
},
|
||||
"mapperUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"mapperFilename": {
|
||||
"type": "string"
|
||||
},
|
||||
"clientId": {
|
||||
"type": "string"
|
||||
},
|
||||
"clientSecret": {
|
||||
"type": "string"
|
||||
},
|
||||
"scopes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"issuerUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"authUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"tokenUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"requestedClaims": {
|
||||
"type": "object"
|
||||
},
|
||||
"predefined": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"callbackUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "OIDCProvider specification",
|
||||
"title": "OIDCProvider Specification"
|
||||
}
|
||||
},
|
||||
"securityDefinitions": {
|
||||
"ApiKeyAuth": {
|
||||
"type": "apiKey",
|
||||
"name": "X-RAFAY-API-KEYID",
|
||||
"in": "header"
|
||||
},
|
||||
"BasicAuth": {
|
||||
"type": "basic"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": [],
|
||||
"BasicAuth": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "proto/types/userpb/v3/oidc_provider.proto",
|
||||
"version": "version not set"
|
||||
},
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"paths": {},
|
||||
"definitions": {
|
||||
"googlerpcStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"protobufAny": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"@type": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,7 @@ var (
|
||||
gs service.GroupService
|
||||
rs service.RoleService
|
||||
is service.IdpService
|
||||
ps service.OIDCProviderService
|
||||
dev bool
|
||||
_log = logv2.GetLogger()
|
||||
authPool authv3.AuthPool
|
||||
@@ -124,6 +125,7 @@ func setup() {
|
||||
gs = service.NewGroupService(db)
|
||||
rs = service.NewRoleService(db)
|
||||
is = service.NewIdpService(db)
|
||||
ps = service.NewOIDCProviderService(db)
|
||||
|
||||
_log.Infow("usermgmt setup complete")
|
||||
}
|
||||
@@ -158,6 +160,7 @@ func runAPI(wg *sync.WaitGroup, ctx context.Context) {
|
||||
pbrpcv3.RegisterGroupHandlerFromEndpoint,
|
||||
pbrpcv3.RegisterRoleHandlerFromEndpoint,
|
||||
pbrpcv3.RegisterIdpHandlerFromEndpoint,
|
||||
pbrpcv3.RegisterOIDCProviderHandlerFromEndpoint,
|
||||
)
|
||||
if err != nil {
|
||||
_log.Fatalw("unable to create gateway", "error", err)
|
||||
@@ -192,6 +195,7 @@ func runRPC(wg *sync.WaitGroup, ctx context.Context) {
|
||||
groupServer := server.NewGroupServer(gs)
|
||||
roleServer := server.NewRoleServer(rs)
|
||||
idpServer := server.NewIdpServer(is)
|
||||
oidcProviderServer := server.NewOIDCServer(ps)
|
||||
|
||||
l, err := net.Listen("tcp", fmt.Sprintf(":%d", rpcPort))
|
||||
if err != nil {
|
||||
@@ -229,6 +233,7 @@ func runRPC(wg *sync.WaitGroup, ctx context.Context) {
|
||||
rpcv3.RegisterGroupServer(s, groupServer)
|
||||
rpcv3.RegisterRoleServer(s, roleServer)
|
||||
rpcv3.RegisterIdpServer(s, idpServer)
|
||||
rpcv3.RegisterOIDCProviderServer(s, oidcProviderServer)
|
||||
|
||||
_log.Infow("starting rpc server", "port", rpcPort)
|
||||
err = s.Serve(l)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type OIDCProvider struct {
|
||||
bun.BaseModel `bun:"table:authsrv_oidc_provider,alias:oidcprovider"`
|
||||
|
||||
Id uuid.UUID `bun:"id,type:uuid,pk,default:uuid_generate_v4()"`
|
||||
Name string `bun:"name,notnull,unique"`
|
||||
Description string `bun:"description"`
|
||||
OrganizationId uuid.UUID `bun:"organization_id,type:uuid"`
|
||||
PartnerId uuid.UUID `bun:"partner_id,type:uuid"`
|
||||
CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp"`
|
||||
ModifiedAt time.Time `bun:"modified_at,notnull,default:current_timestamp"`
|
||||
|
||||
ProviderName string `bun:"provider_name,notnull"`
|
||||
MapperURL string `bun:"mapper_url"`
|
||||
MapperFilename string `bun:"mapper_filename"`
|
||||
ClientId string `bun:"client_id,notnull"`
|
||||
ClientSecret string `bun:"client_secret,notnull"`
|
||||
Scopes []string `bun:"scopes,notnull"`
|
||||
IssuerURL string `bun:"issuer_url,notnull"`
|
||||
AuthURL string `bun:"auth_url"`
|
||||
TokenURL string `bun:"token_url"`
|
||||
RequestedClaims map[string]interface{} `bun:"type:jsonb"`
|
||||
Predefined bool `bun:"predefined,notnull"`
|
||||
Trash bool `bun:"trash,default:false"`
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/RafaySystems/rcloud-base/components/usermgmt/pkg/service"
|
||||
rpcv3 "github.com/RafaySystems/rcloud-base/components/usermgmt/proto/rpc/v3"
|
||||
userv3 "github.com/RafaySystems/rcloud-base/components/usermgmt/proto/types/userpb/v3"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
type oidcProvider struct {
|
||||
service.OIDCProviderService
|
||||
}
|
||||
|
||||
func NewOIDCServer(providerSvc service.OIDCProviderService) rpcv3.OIDCProviderServer {
|
||||
return &oidcProvider{providerSvc}
|
||||
}
|
||||
|
||||
func (s *oidcProvider) CreateOIDCProvider(ctx context.Context, p *userv3.OIDCProvider) (*userv3.OIDCProvider, error) {
|
||||
return s.Create(ctx, p)
|
||||
}
|
||||
func (s *oidcProvider) GetOIDCProvider(ctx context.Context, p *userv3.OIDCProvider) (*userv3.OIDCProvider, error) {
|
||||
return s.GetByID(ctx, p)
|
||||
}
|
||||
func (s *oidcProvider) ListOIDCProvider(ctx context.Context, p *emptypb.Empty) (*userv3.OIDCProviderList, error) {
|
||||
return s.List(ctx)
|
||||
}
|
||||
func (s *oidcProvider) UpdateOIDCProvider(ctx context.Context, p *userv3.OIDCProvider) (*userv3.OIDCProvider, error) {
|
||||
return s.Update(ctx, p)
|
||||
}
|
||||
func (s *oidcProvider) DeleteOIDCProvider(ctx context.Context, p *userv3.OIDCProvider) (*emptypb.Empty, error) {
|
||||
// TODO: if successful return 204 NO CONTENT
|
||||
return &emptypb.Empty{}, s.Delete(ctx, p)
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/RafaySystems/rcloud-base/components/common/pkg/persistence/provider/pg"
|
||||
commonv3 "github.com/RafaySystems/rcloud-base/components/common/proto/types/commonpb/v3"
|
||||
"github.com/RafaySystems/rcloud-base/components/usermgmt/pkg/internal/models"
|
||||
userv3 "github.com/RafaySystems/rcloud-base/components/usermgmt/proto/types/userpb/v3"
|
||||
"github.com/google/uuid"
|
||||
bun "github.com/uptrace/bun"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
|
||||
type OIDCProviderService interface {
|
||||
Create(context.Context, *userv3.OIDCProvider) (*userv3.OIDCProvider, error)
|
||||
GetByID(context.Context, *userv3.OIDCProvider) (*userv3.OIDCProvider, error)
|
||||
List(context.Context) (*userv3.OIDCProviderList, error)
|
||||
Update(context.Context, *userv3.OIDCProvider) (*userv3.OIDCProvider, error)
|
||||
Delete(context.Context, *userv3.OIDCProvider) error
|
||||
}
|
||||
|
||||
type oidcProvider struct {
|
||||
dao pg.EntityDAO
|
||||
}
|
||||
|
||||
func NewOIDCProviderService(db *bun.DB) OIDCProviderService {
|
||||
return &oidcProvider{
|
||||
dao: pg.NewEntityDAO(db),
|
||||
}
|
||||
}
|
||||
|
||||
func generateCallbackUrl(id string) string {
|
||||
base := os.Getenv("KRATOS_PUBLIC_URL")
|
||||
return fmt.Sprintf("%s/self-service/methods/oidc/callback/%s", base, id)
|
||||
}
|
||||
|
||||
func validateURL(rawURL string) error {
|
||||
_, err := url.ParseRequestURI(rawURL)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *oidcProvider) Create(ctx context.Context, provider *userv3.OIDCProvider) (*userv3.OIDCProvider, error) {
|
||||
// validate name
|
||||
name := provider.Metadata.GetName()
|
||||
if len(name) == 0 {
|
||||
return &userv3.OIDCProvider{}, fmt.Errorf("EMPTY NAME")
|
||||
}
|
||||
e := &models.OIDCProvider{}
|
||||
s.dao.GetByName(ctx, name, e)
|
||||
if e.Name == name {
|
||||
return &userv3.OIDCProvider{}, fmt.Errorf("DUPLICATE NAME")
|
||||
}
|
||||
|
||||
mapUrl := provider.Spec.GetMapperUrl()
|
||||
issUrl := provider.Spec.GetIssuerUrl()
|
||||
authUrl := provider.Spec.GetAuthUrl()
|
||||
tknUrl := provider.Spec.GetTokenUrl()
|
||||
|
||||
if len(mapUrl) != 0 && validateURL(mapUrl) != nil {
|
||||
return &userv3.OIDCProvider{}, fmt.Errorf("INVALID MAPPER URL")
|
||||
}
|
||||
if len(issUrl) != 0 && validateURL(issUrl) != nil {
|
||||
return &userv3.OIDCProvider{}, fmt.Errorf("INVALID ISSUER URL")
|
||||
}
|
||||
if len(authUrl) != 0 && validateURL(authUrl) != nil {
|
||||
return &userv3.OIDCProvider{}, fmt.Errorf("INVALID AUTH URL")
|
||||
}
|
||||
if len(tknUrl) != 0 && validateURL(tknUrl) != nil {
|
||||
return &userv3.OIDCProvider{}, fmt.Errorf("INVALID TOKEN URL")
|
||||
}
|
||||
|
||||
entity := &models.OIDCProvider{
|
||||
Name: name,
|
||||
CreatedAt: time.Time{},
|
||||
ModifiedAt: time.Time{},
|
||||
ProviderName: provider.Spec.GetProviderName(),
|
||||
MapperURL: mapUrl,
|
||||
MapperFilename: provider.Spec.GetMapperFilename(),
|
||||
ClientId: provider.Spec.GetClientId(),
|
||||
ClientSecret: provider.Spec.GetClientSecret(),
|
||||
Scopes: provider.Spec.GetScopes(),
|
||||
IssuerURL: issUrl,
|
||||
AuthURL: authUrl,
|
||||
TokenURL: tknUrl,
|
||||
RequestedClaims: provider.Spec.GetRequestedClaims().AsMap(),
|
||||
Predefined: provider.Spec.GetPredefined(),
|
||||
}
|
||||
_, err := s.dao.Create(ctx, entity)
|
||||
if err != nil {
|
||||
return &userv3.OIDCProvider{}, err
|
||||
}
|
||||
|
||||
rclaims, _ := structpb.NewStruct(entity.RequestedClaims)
|
||||
rv := &userv3.OIDCProvider{
|
||||
ApiVersion: "usermgmt.k8smgmt.io/v3",
|
||||
Kind: "OIDCProvider",
|
||||
Metadata: &commonv3.Metadata{
|
||||
Name: entity.Name,
|
||||
Description: entity.Description,
|
||||
Id: entity.Id.String(),
|
||||
},
|
||||
Spec: &userv3.OIDCProviderSpec{
|
||||
ProviderName: entity.ProviderName,
|
||||
MapperUrl: entity.MapperURL,
|
||||
MapperFilename: entity.MapperFilename,
|
||||
ClientId: entity.ClientId,
|
||||
ClientSecret: entity.ClientSecret,
|
||||
Scopes: entity.Scopes,
|
||||
IssuerUrl: entity.IssuerURL,
|
||||
AuthUrl: entity.AuthURL,
|
||||
TokenUrl: entity.TokenURL,
|
||||
RequestedClaims: rclaims,
|
||||
Predefined: entity.Predefined,
|
||||
CallbackUrl: generateCallbackUrl(entity.Id.String()),
|
||||
},
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
func (s *oidcProvider) GetByID(ctx context.Context, provider *userv3.OIDCProvider) (*userv3.OIDCProvider, error) {
|
||||
id, err := uuid.Parse(provider.Metadata.GetId())
|
||||
if err != nil {
|
||||
return &userv3.OIDCProvider{}, err
|
||||
}
|
||||
|
||||
entity := &models.OIDCProvider{}
|
||||
_, err = s.dao.GetByID(ctx, id, entity)
|
||||
// TODO: Return proper error for Id not exist
|
||||
if err != nil {
|
||||
return &userv3.OIDCProvider{}, err
|
||||
}
|
||||
|
||||
rclaims, _ := structpb.NewStruct(entity.RequestedClaims)
|
||||
rv := &userv3.OIDCProvider{
|
||||
ApiVersion: "usermgmt.k8smgmt.io/v3",
|
||||
Kind: "OIDCProvider",
|
||||
Metadata: &commonv3.Metadata{
|
||||
Name: entity.Name,
|
||||
Description: entity.Description,
|
||||
Id: entity.Id.String(),
|
||||
},
|
||||
Spec: &userv3.OIDCProviderSpec{
|
||||
ProviderName: entity.ProviderName,
|
||||
MapperUrl: entity.MapperURL,
|
||||
MapperFilename: entity.MapperFilename,
|
||||
ClientId: entity.ClientId,
|
||||
Scopes: entity.Scopes,
|
||||
IssuerUrl: entity.IssuerURL,
|
||||
AuthUrl: entity.AuthURL,
|
||||
TokenUrl: entity.TokenURL,
|
||||
RequestedClaims: rclaims,
|
||||
Predefined: entity.Predefined,
|
||||
CallbackUrl: generateCallbackUrl(entity.Id.String()),
|
||||
},
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
func (s *oidcProvider) List(ctx context.Context) (*userv3.OIDCProviderList, error) {
|
||||
var (
|
||||
entities []models.OIDCProvider
|
||||
orgID uuid.NullUUID
|
||||
parID uuid.NullUUID
|
||||
)
|
||||
_, err := s.dao.List(ctx, parID, orgID, &entities)
|
||||
if err != nil {
|
||||
return &userv3.OIDCProviderList{}, nil
|
||||
}
|
||||
var result []*userv3.OIDCProvider
|
||||
for _, entity := range entities {
|
||||
rclaims, _ := structpb.NewStruct(entity.RequestedClaims)
|
||||
e := &userv3.OIDCProvider{
|
||||
ApiVersion: "usermgmt.k8smgmt.io/v3",
|
||||
Kind: "OIDCProvider",
|
||||
Metadata: &commonv3.Metadata{
|
||||
Name: entity.Name,
|
||||
Description: entity.Description,
|
||||
Id: entity.Id.String(),
|
||||
},
|
||||
Spec: &userv3.OIDCProviderSpec{
|
||||
ProviderName: entity.ProviderName,
|
||||
MapperUrl: entity.MapperURL,
|
||||
MapperFilename: entity.MapperFilename,
|
||||
ClientId: entity.ClientId,
|
||||
Scopes: entity.Scopes,
|
||||
IssuerUrl: entity.IssuerURL,
|
||||
AuthUrl: entity.AuthURL,
|
||||
TokenUrl: entity.TokenURL,
|
||||
RequestedClaims: rclaims,
|
||||
Predefined: entity.Predefined,
|
||||
CallbackUrl: generateCallbackUrl(entity.Id.String()),
|
||||
},
|
||||
}
|
||||
result = append(result, e)
|
||||
}
|
||||
|
||||
rv := &userv3.OIDCProviderList{
|
||||
ApiVersion: "usermgmt.k8smgmt.io/v3",
|
||||
Kind: "OIDCProviderList",
|
||||
Items: result,
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
func (s *oidcProvider) Update(ctx context.Context, provider *userv3.OIDCProvider) (*userv3.OIDCProvider, error) {
|
||||
var id, orgId, partId uuid.UUID
|
||||
id, err := uuid.Parse(provider.Metadata.GetId())
|
||||
// TODO: 400 Bad Request
|
||||
if err != nil {
|
||||
return &userv3.OIDCProvider{}, err
|
||||
}
|
||||
if len(provider.Metadata.GetOrganization()) != 0 {
|
||||
orgId, err = uuid.Parse(provider.Metadata.GetOrganization())
|
||||
if err != nil {
|
||||
return &userv3.OIDCProvider{}, err
|
||||
}
|
||||
}
|
||||
if len(provider.Metadata.GetPartner()) != 0 {
|
||||
partId, err = uuid.Parse(provider.Metadata.GetPartner())
|
||||
if err != nil {
|
||||
return &userv3.OIDCProvider{}, err
|
||||
}
|
||||
}
|
||||
_, err = s.dao.GetByID(ctx, id, &models.OIDCProvider{})
|
||||
// TODO: Return proper error for Id not exist
|
||||
if err != nil {
|
||||
return &userv3.OIDCProvider{}, err
|
||||
}
|
||||
|
||||
mapUrl := provider.Spec.GetMapperUrl()
|
||||
issUrl := provider.Spec.GetIssuerUrl()
|
||||
authUrl := provider.Spec.GetAuthUrl()
|
||||
tknUrl := provider.Spec.GetTokenUrl()
|
||||
|
||||
if len(mapUrl) != 0 && validateURL(mapUrl) != nil {
|
||||
return &userv3.OIDCProvider{}, fmt.Errorf("INVALID MAPPER URL")
|
||||
}
|
||||
if len(issUrl) != 0 && validateURL(issUrl) != nil {
|
||||
return &userv3.OIDCProvider{}, fmt.Errorf("INVALID ISSUER URL")
|
||||
}
|
||||
if len(authUrl) != 0 && validateURL(authUrl) != nil {
|
||||
return &userv3.OIDCProvider{}, fmt.Errorf("INVALID AUTH URL")
|
||||
}
|
||||
if len(tknUrl) != 0 && validateURL(tknUrl) != nil {
|
||||
return &userv3.OIDCProvider{}, fmt.Errorf("INVALID TOKEN URL")
|
||||
}
|
||||
|
||||
entity := &models.OIDCProvider{
|
||||
Id: id,
|
||||
Name: provider.Metadata.GetName(),
|
||||
Description: provider.Metadata.GetDescription(),
|
||||
OrganizationId: orgId,
|
||||
PartnerId: partId,
|
||||
ModifiedAt: time.Now(),
|
||||
ProviderName: provider.Spec.GetProviderName(),
|
||||
MapperURL: mapUrl,
|
||||
MapperFilename: provider.Spec.GetMapperFilename(),
|
||||
ClientId: provider.Spec.GetClientId(),
|
||||
ClientSecret: provider.Spec.GetClientSecret(),
|
||||
Scopes: provider.Spec.GetScopes(),
|
||||
IssuerURL: issUrl,
|
||||
AuthURL: authUrl,
|
||||
TokenURL: tknUrl,
|
||||
RequestedClaims: provider.Spec.GetRequestedClaims().AsMap(),
|
||||
Predefined: provider.Spec.GetPredefined(),
|
||||
}
|
||||
_, err = s.dao.Update(ctx, id, entity)
|
||||
if err != nil {
|
||||
return &userv3.OIDCProvider{}, err
|
||||
}
|
||||
|
||||
rclaims, _ := structpb.NewStruct(entity.RequestedClaims)
|
||||
rv := &userv3.OIDCProvider{
|
||||
ApiVersion: "usermgmt.k8smgmt.io/v3",
|
||||
Kind: "OIDCProvider",
|
||||
Metadata: &commonv3.Metadata{
|
||||
Name: entity.Name,
|
||||
Description: entity.Description,
|
||||
Id: entity.Id.String(),
|
||||
},
|
||||
Spec: &userv3.OIDCProviderSpec{
|
||||
ProviderName: entity.ProviderName,
|
||||
MapperUrl: entity.MapperURL,
|
||||
MapperFilename: entity.MapperFilename,
|
||||
ClientId: entity.ClientId,
|
||||
ClientSecret: entity.ClientSecret,
|
||||
Scopes: entity.Scopes,
|
||||
IssuerUrl: entity.IssuerURL,
|
||||
AuthUrl: entity.AuthURL,
|
||||
TokenUrl: entity.TokenURL,
|
||||
RequestedClaims: rclaims,
|
||||
Predefined: entity.Predefined,
|
||||
CallbackUrl: generateCallbackUrl(entity.Id.String()),
|
||||
},
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
func (s *oidcProvider) Delete(ctx context.Context, provider *userv3.OIDCProvider) error {
|
||||
id, err := uuid.Parse(provider.Metadata.GetId())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
entity := &models.OIDCProvider{}
|
||||
_, err = s.dao.GetByID(ctx, id, entity)
|
||||
if entity.Id != id {
|
||||
return fmt.Errorf("ID DOES NOT EXISTS")
|
||||
}
|
||||
|
||||
err = s.dao.Delete(ctx, id, &models.OIDCProvider{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
syntax = "proto3";
|
||||
package rafay.dev.rpc.v3;
|
||||
|
||||
import "google/api/annotations.proto";
|
||||
import "google/protobuf/empty.proto";
|
||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||
import "proto/types/userpb/v3/oidc_provider.proto";
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
|
||||
info : {
|
||||
title : "OIdC Provider Service"
|
||||
version : "2.0"
|
||||
contact : {name : "Rafay Dev"}
|
||||
}
|
||||
schemes : HTTPS
|
||||
consumes : "application/json"
|
||||
consumes : "application/yaml"
|
||||
produces : "application/json"
|
||||
produces : "application/yaml"
|
||||
security_definitions : {
|
||||
security : {
|
||||
key : "BasicAuth"
|
||||
value : {type : TYPE_BASIC}
|
||||
}
|
||||
security : {
|
||||
key : "ApiKeyAuth"
|
||||
value : {type : TYPE_API_KEY in : IN_HEADER name : "X-RAFAY-API-KEYID"}
|
||||
}
|
||||
}
|
||||
security : {
|
||||
security_requirement : {
|
||||
key : "BasicAuth"
|
||||
value : {}
|
||||
}
|
||||
security_requirement : {
|
||||
key : "ApiKeyAuth"
|
||||
value : {}
|
||||
}
|
||||
}
|
||||
responses : {
|
||||
key : "403"
|
||||
value : {
|
||||
description : "Returned when the user does not have permission to access "
|
||||
"the resource."
|
||||
}
|
||||
}
|
||||
responses : {
|
||||
key : "404"
|
||||
value : {
|
||||
description : "Returned when the resource does not exist."
|
||||
schema : {json_schema : {type : STRING}}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
service OIDCProvider {
|
||||
rpc CreateOIDCProvider(rafay.dev.types.user.v3.OIDCProvider) returns (rafay.dev.types.user.v3.OIDCProvider) {
|
||||
option (google.api.http) = {
|
||||
post : "/auth/v3/sso/oidc/provider"
|
||||
body : "*"
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
responses : {
|
||||
key : "201"
|
||||
value : {description : "Returned when oidc provider is created successfully."}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
rpc GetOIDCProvider(rafay.dev.types.user.v3.OIDCProvider) returns (rafay.dev.types.user.v3.OIDCProvider) {
|
||||
option (google.api.http) = {
|
||||
get : "/auth/v3/sso/oidc/provider/{metadata.id}"
|
||||
};
|
||||
};
|
||||
|
||||
rpc ListOIDCProvider(google.protobuf.Empty) returns (rafay.dev.types.user.v3.OIDCProviderList) {
|
||||
option (google.api.http) = {
|
||||
get : "/auth/v3/sso/oidc/provider"
|
||||
};
|
||||
};
|
||||
|
||||
rpc UpdateOIDCProvider(rafay.dev.types.user.v3.OIDCProvider) returns (rafay.dev.types.user.v3.OIDCProvider) {
|
||||
option (google.api.http) = {
|
||||
put : "/auth/v3/sso/oidc/provider/{metadata.id}"
|
||||
body : "*"
|
||||
};
|
||||
};
|
||||
|
||||
rpc DeleteOIDCProvider(rafay.dev.types.user.v3.OIDCProvider) returns (google.protobuf.Empty) {
|
||||
option (google.api.http) = {
|
||||
delete : "/auth/v3/sso/oidc/provider/{metadata.id}"
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
responses : {
|
||||
key : "204"
|
||||
value : {
|
||||
description : "Returned when oidc provider is deleted successfully."
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
syntax = "proto3";
|
||||
package rafay.dev.types.user.v3;
|
||||
|
||||
import "proto/types/commonpb/v3/common.proto";
|
||||
import "google/protobuf/struct.proto";
|
||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||
|
||||
message OIDCProvider {
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
|
||||
json_schema : {
|
||||
title : "OIDCProvider"
|
||||
description : "Open Id Connect Provider"
|
||||
required : [ "apiVersion", "kind", "metadata", "spec" ]
|
||||
}
|
||||
};
|
||||
|
||||
string apiVersion = 1
|
||||
[ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
title : "API Version",
|
||||
description : "API Version of the OIDCProvider resource"
|
||||
default : "usermgmt.k8smgmt.io/v3"
|
||||
} ];
|
||||
string kind = 2
|
||||
[ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
title : "Kind",
|
||||
description : "Kind of the OIDCProvider resource"
|
||||
default : "OIDCProvider"
|
||||
} ];
|
||||
rafay.dev.types.common.v3.Metadata metadata = 3
|
||||
[ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
title : "Metadata",
|
||||
description : "Metadata of the OIDCProvider resource"
|
||||
} ];
|
||||
OIDCProviderSpec spec = 4
|
||||
[ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
title : "Spec",
|
||||
description : "Spec of the OIDCProvider resource"
|
||||
} ];
|
||||
|
||||
rafay.dev.types.common.v3.Status status = 5
|
||||
[ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
title : "Status",
|
||||
description : "Status of the resource"
|
||||
read_only : true
|
||||
} ];
|
||||
}
|
||||
|
||||
message OIDCProviderSpec {
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
|
||||
json_schema : {
|
||||
title : "OIDCProvider Specification"
|
||||
description : "OIDCProvider specification"
|
||||
}
|
||||
};
|
||||
string providerName = 1; // enumeration?
|
||||
string mapperUrl = 2;
|
||||
string mapperFilename = 3;
|
||||
string clientId = 4;
|
||||
string clientSecret = 5;
|
||||
repeated string scopes = 6;
|
||||
string issuerUrl = 7;
|
||||
string authUrl = 8;
|
||||
string tokenUrl = 9;
|
||||
google.protobuf.Struct requestedClaims = 10; // JSON object
|
||||
bool predefined = 11;
|
||||
string callbackUrl = 12;
|
||||
}
|
||||
|
||||
message OIDCProviderList {
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
|
||||
json_schema : {
|
||||
title : "OIDCProviderList"
|
||||
description : "OIDC providers list"
|
||||
read_only : true
|
||||
}
|
||||
};
|
||||
string apiVersion = 1
|
||||
[ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
title : "API Version",
|
||||
description : "API Version of the OIDCProvider list resource"
|
||||
default : "usermgmt.k8smgmt.io/v3"
|
||||
read_only : true
|
||||
} ];
|
||||
string kind = 2
|
||||
[ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
title : "Kind",
|
||||
description : "Kind of the OIDCProvider list resource"
|
||||
default : "OIDCProviderList"
|
||||
read_only : true
|
||||
} ];
|
||||
rafay.dev.types.common.v3.ListMetadata metadata = 3
|
||||
[ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
title : "Metadata",
|
||||
description : "Metadata of the OIDCProvider list resource"
|
||||
read_only : true
|
||||
} ];
|
||||
repeated OIDCProvider items = 4
|
||||
[ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
title : "Items",
|
||||
description : "List of the OIDCProvider resources"
|
||||
read_only : true
|
||||
} ];
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
CREATE FUNCTION providers_after_change_trigger()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
PERFORM pg_notify('provider:changed', '');
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER providers_updated
|
||||
AFTER UPDATE ON authsrv_oidc_provider
|
||||
FOR EACH ROW EXECUTE PROCEDURE providers_after_change_trigger();
|
||||
|
||||
CREATE TRIGGER providers_inserted
|
||||
AFTER INSERT ON authsrv_oidc_provider
|
||||
FOR EACH ROW EXECUTE PROCEDURE providers_after_change_trigger();
|
||||
|
||||
CREATE TRIGGER providers_deleted
|
||||
AFTER DELETE ON authsrv_oidc_provider
|
||||
FOR EACH ROW EXECUTE PROCEDURE providers_after_change_trigger();
|
||||
Reference in New Issue
Block a user