diff --git a/components/usermgmt/_kratos/go.mod b/components/usermgmt/_kratos/go.mod new file mode 100644 index 0000000..3a418c4 --- /dev/null +++ b/components/usermgmt/_kratos/go.mod @@ -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 +) diff --git a/components/usermgmt/_kratos/go.sum b/components/usermgmt/_kratos/go.sum new file mode 100644 index 0000000..f7a1746 --- /dev/null +++ b/components/usermgmt/_kratos/go.sum @@ -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= diff --git a/components/usermgmt/_kratos/kratos.yml b/components/usermgmt/_kratos/kratos.yml index 2e5ce71..acec395 100644 --- a/components/usermgmt/_kratos/kratos.yml +++ b/components/usermgmt/_kratos/kratos.yml @@ -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 diff --git a/components/usermgmt/_kratos/oidc_providers_example.yml b/components/usermgmt/_kratos/oidc_providers_example.yml new file mode 100644 index 0000000..b20897e --- /dev/null +++ b/components/usermgmt/_kratos/oidc_providers_example.yml @@ -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 diff --git a/components/usermgmt/_kratos/sync_providers_from_db.go b/components/usermgmt/_kratos/sync_providers_from_db.go new file mode 100644 index 0000000..e060c80 --- /dev/null +++ b/components/usermgmt/_kratos/sync_providers_from_db.go @@ -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") + } + } + +} diff --git a/components/usermgmt/gen/openapi/proto/rpc/v3/oidc_provider.swagger.json b/components/usermgmt/gen/openapi/proto/rpc/v3/oidc_provider.swagger.json new file mode 100644 index 0000000..bb65ac8 --- /dev/null +++ b/components/usermgmt/gen/openapi/proto/rpc/v3/oidc_provider.swagger.json @@ -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": [] + } + ] +} diff --git a/components/usermgmt/gen/openapi/proto/types/userpb/v3/oidc_provider.swagger.json b/components/usermgmt/gen/openapi/proto/types/userpb/v3/oidc_provider.swagger.json new file mode 100644 index 0000000..c6916b5 --- /dev/null +++ b/components/usermgmt/gen/openapi/proto/types/userpb/v3/oidc_provider.swagger.json @@ -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": {} + } + } +} diff --git a/components/usermgmt/main.go b/components/usermgmt/main.go index 22754e3..4b12dc0 100644 --- a/components/usermgmt/main.go +++ b/components/usermgmt/main.go @@ -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) diff --git a/components/usermgmt/pkg/internal/models/oidc_provider.go b/components/usermgmt/pkg/internal/models/oidc_provider.go new file mode 100644 index 0000000..e24e30a --- /dev/null +++ b/components/usermgmt/pkg/internal/models/oidc_provider.go @@ -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"` +} diff --git a/components/usermgmt/pkg/server/oidc_provider.go b/components/usermgmt/pkg/server/oidc_provider.go new file mode 100644 index 0000000..8a7c365 --- /dev/null +++ b/components/usermgmt/pkg/server/oidc_provider.go @@ -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) +} diff --git a/components/usermgmt/pkg/service/oidc_provider.go b/components/usermgmt/pkg/service/oidc_provider.go new file mode 100644 index 0000000..7e10098 --- /dev/null +++ b/components/usermgmt/pkg/service/oidc_provider.go @@ -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 +} diff --git a/components/usermgmt/proto/rpc/v3/oidc_provider.proto b/components/usermgmt/proto/rpc/v3/oidc_provider.proto new file mode 100644 index 0000000..68878fc --- /dev/null +++ b/components/usermgmt/proto/rpc/v3/oidc_provider.proto @@ -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." + } + } + }; + }; +} \ No newline at end of file diff --git a/components/usermgmt/proto/types/userpb/v3/oidc_provider.proto b/components/usermgmt/proto/types/userpb/v3/oidc_provider.proto new file mode 100644 index 0000000..895f2b8 --- /dev/null +++ b/components/usermgmt/proto/types/userpb/v3/oidc_provider.proto @@ -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 + } ]; +} diff --git a/persistence/migrations/admindb/pg_oidc_provider_triggers.sql b/persistence/migrations/admindb/pg_oidc_provider_triggers.sql new file mode 100644 index 0000000..9d8d503 --- /dev/null +++ b/persistence/migrations/admindb/pg_oidc_provider_triggers.sql @@ -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();