From de7e4918028ea06d1ff3da4a3f20ec1d6195101e Mon Sep 17 00:00:00 2001 From: Akshay Gaikwad Date: Thu, 20 Jan 2022 11:18:58 +0530 Subject: [PATCH 01/12] Add OIdC provider service API specs --- .../proto/rpc/v3/oidc_provider.swagger.json | 875 ++++++++++++++++++ .../userpb/v3/oidc_provider.swagger.json | 43 + .../usermgmt/proto/rpc/v3/oidc_provider.proto | 104 +++ .../proto/types/userpb/v3/oidc_provider.proto | 103 +++ 4 files changed, 1125 insertions(+) create mode 100644 components/usermgmt/gen/openapi/proto/rpc/v3/oidc_provider.swagger.json create mode 100644 components/usermgmt/gen/openapi/proto/types/userpb/v3/oidc_provider.swagger.json create mode 100644 components/usermgmt/proto/rpc/v3/oidc_provider.proto create mode 100644 components/usermgmt/proto/types/userpb/v3/oidc_provider.proto 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/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 + } ]; +} From aa98bcd215f59cfe247b2b9b50a5939790719f09 Mon Sep 17 00:00:00 2001 From: Akshay Gaikwad Date: Thu, 20 Jan 2022 11:41:05 +0530 Subject: [PATCH 02/12] Add OIDC provider db model --- .../pkg/internal/models/oidc_provider.go | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 components/usermgmt/pkg/internal/models/oidc_provider.go 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..1d028c7 --- /dev/null +++ b/components/usermgmt/pkg/internal/models/oidc_provider.go @@ -0,0 +1,32 @@ +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"` + 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"` + CallbackURL string `bun:"callback_url,notnull"` + Trash bool `bun:"trash,default:false"` +} From f93962e5f20ae2cbc21236cf256fde9c539596e7 Mon Sep 17 00:00:00 2001 From: Akshay Gaikwad Date: Thu, 20 Jan 2022 11:41:43 +0530 Subject: [PATCH 03/12] Add structure for OIdC provider service --- .../usermgmt/pkg/server/oidc_provider.go | 34 ++++++++++++++ .../usermgmt/pkg/service/oidc_provider.go | 47 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 components/usermgmt/pkg/server/oidc_provider.go create mode 100644 components/usermgmt/pkg/service/oidc_provider.go diff --git a/components/usermgmt/pkg/server/oidc_provider.go b/components/usermgmt/pkg/server/oidc_provider.go new file mode 100644 index 0000000..68c8418 --- /dev/null +++ b/components/usermgmt/pkg/server/oidc_provider.go @@ -0,0 +1,34 @@ +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) { + 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..415d4f8 --- /dev/null +++ b/components/usermgmt/pkg/service/oidc_provider.go @@ -0,0 +1,47 @@ +package service + +import ( + "context" + + "github.com/RafaySystems/rcloud-base/components/common/pkg/persistence/provider/pg" + userv3 "github.com/RafaySystems/rcloud-base/components/usermgmt/proto/types/userpb/v3" + bun "github.com/uptrace/bun" +) + +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 (s *oidcProvider) Create(ctx context.Context, provider *userv3.OIDCProvider) (*userv3.OIDCProvider, error) { + return &userv3.OIDCProvider{}, nil +} + +func (s *oidcProvider) GetByID(ctx context.Context, provider *userv3.OIDCProvider) (*userv3.OIDCProvider, error) { + return &userv3.OIDCProvider{}, nil +} + +func (s *oidcProvider) List(ctx context.Context) (*userv3.OIDCProviderList, error) { + return &userv3.OIDCProviderList{}, nil +} + +func (s *oidcProvider) Update(ctx context.Context, provider *userv3.OIDCProvider) (*userv3.OIDCProvider, error) { + return &userv3.OIDCProvider{}, nil +} + +func (s *oidcProvider) Delete(ctx context.Context, provider *userv3.OIDCProvider) error { + return nil +} From 9902eb4af0ef33485b3a14a0c4f7adab338a6b82 Mon Sep 17 00:00:00 2001 From: Akshay Gaikwad Date: Thu, 20 Jan 2022 13:57:26 +0530 Subject: [PATCH 04/12] Add organizationId and partnerId to OIDC provider model --- .../usermgmt/pkg/internal/models/oidc_provider.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/components/usermgmt/pkg/internal/models/oidc_provider.go b/components/usermgmt/pkg/internal/models/oidc_provider.go index 1d028c7..6002dc6 100644 --- a/components/usermgmt/pkg/internal/models/oidc_provider.go +++ b/components/usermgmt/pkg/internal/models/oidc_provider.go @@ -10,11 +10,13 @@ import ( 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"` - CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp"` - ModifiedAt time.Time `bun:"modified_at,notnull,default:current_timestamp"` + 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"` From a6e6827faca04058bccbd626d5b83e5993a209af Mon Sep 17 00:00:00 2001 From: Akshay Gaikwad Date: Thu, 20 Jan 2022 13:58:49 +0530 Subject: [PATCH 05/12] Implement create, get, list endpoint of oidc provider service --- components/usermgmt/main.go | 5 + .../usermgmt/pkg/service/oidc_provider.go | 161 +++++++++++++++++- 2 files changed, 163 insertions(+), 3 deletions(-) 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/service/oidc_provider.go b/components/usermgmt/pkg/service/oidc_provider.go index 415d4f8..c400f86 100644 --- a/components/usermgmt/pkg/service/oidc_provider.go +++ b/components/usermgmt/pkg/service/oidc_provider.go @@ -2,10 +2,18 @@ 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 { @@ -26,16 +34,163 @@ func NewOIDCProviderService(db *bun.DB) OIDCProviderService { } } +func generateCallbackUrl() (string, error) { + base, err := url.Parse(os.Getenv("APP_HOST_HTTP")) + if err != nil { + return "", err + } + uuid := uuid.New() + return fmt.Sprintf("%s/auth/v3/sso/callback/%s", base, uuid), nil +} + func (s *oidcProvider) Create(ctx context.Context, provider *userv3.OIDCProvider) (*userv3.OIDCProvider, error) { - return &userv3.OIDCProvider{}, nil + // 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") + } + + callback, err := generateCallbackUrl() + if err != nil { + return &userv3.OIDCProvider{}, err + } + entity := &models.OIDCProvider{ + Name: name, + CreatedAt: time.Time{}, + ModifiedAt: time.Time{}, + ProviderName: provider.Spec.GetProviderName(), + MapperURL: provider.Spec.GetMapperUrl(), + MapperFilename: provider.Spec.GetMapperFilename(), + ClientId: provider.Spec.GetClientId(), + ClientSecret: provider.Spec.GetClientSecret(), + Scopes: provider.Spec.GetScopes(), + IssuerURL: provider.Spec.GetIssuerUrl(), + AuthURL: provider.Spec.GetAuthUrl(), + TokenURL: provider.Spec.GetTokenUrl(), + RequestedClaims: provider.Spec.GetRequestedClaims().AsMap(), + Predefined: provider.Spec.GetPredefined(), + CallbackURL: callback, + } + _, 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: entity.CallbackURL, + }, + } + return rv, nil } func (s *oidcProvider) GetByID(ctx context.Context, provider *userv3.OIDCProvider) (*userv3.OIDCProvider, error) { - return &userv3.OIDCProvider{}, nil + 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: entity.CallbackURL, + }, + } + return rv, nil } func (s *oidcProvider) List(ctx context.Context) (*userv3.OIDCProviderList, error) { - return &userv3.OIDCProviderList{}, nil + 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: entity.CallbackURL, + }, + } + 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) { From d5b8fa346681d7975ce8b45b9f5b336eaa5a1700 Mon Sep 17 00:00:00 2001 From: Akshay Gaikwad Date: Fri, 21 Jan 2022 12:17:55 +0530 Subject: [PATCH 06/12] Implement OIDC provider Update and Delete methods --- .../usermgmt/pkg/server/oidc_provider.go | 1 + .../usermgmt/pkg/service/oidc_provider.go | 88 ++++++++++++++++++- 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/components/usermgmt/pkg/server/oidc_provider.go b/components/usermgmt/pkg/server/oidc_provider.go index 68c8418..8a7c365 100644 --- a/components/usermgmt/pkg/server/oidc_provider.go +++ b/components/usermgmt/pkg/server/oidc_provider.go @@ -30,5 +30,6 @@ func (s *oidcProvider) UpdateOIDCProvider(ctx context.Context, p *userv3.OIDCPro 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 index c400f86..929e9dd 100644 --- a/components/usermgmt/pkg/service/oidc_provider.go +++ b/components/usermgmt/pkg/service/oidc_provider.go @@ -194,9 +194,95 @@ func (s *oidcProvider) List(ctx context.Context) (*userv3.OIDCProviderList, erro } func (s *oidcProvider) Update(ctx context.Context, provider *userv3.OIDCProvider) (*userv3.OIDCProvider, error) { - return &userv3.OIDCProvider{}, nil + 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 + } + 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: provider.Spec.GetMapperUrl(), + MapperFilename: provider.Spec.GetMapperFilename(), + ClientId: provider.Spec.GetClientId(), + ClientSecret: provider.Spec.GetClientSecret(), + Scopes: provider.Spec.GetScopes(), + IssuerURL: provider.Spec.GetIssuerUrl(), + AuthURL: provider.Spec.GetAuthUrl(), + TokenURL: provider.Spec.GetTokenUrl(), + RequestedClaims: provider.Spec.GetRequestedClaims().AsMap(), + Predefined: provider.Spec.GetPredefined(), + CallbackURL: provider.Spec.GetCallbackUrl(), + } + _, 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: entity.CallbackURL, + }, + } + 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 } From f01c3a59ae812d8263c554aef1ae3cd9f5bd5f8d Mon Sep 17 00:00:00 2001 From: Akshay Gaikwad Date: Fri, 21 Jan 2022 16:18:54 +0530 Subject: [PATCH 07/12] Declare baseUrl at package level --- .../usermgmt/pkg/service/oidc_provider.go | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/components/usermgmt/pkg/service/oidc_provider.go b/components/usermgmt/pkg/service/oidc_provider.go index 929e9dd..38b6cb4 100644 --- a/components/usermgmt/pkg/service/oidc_provider.go +++ b/components/usermgmt/pkg/service/oidc_provider.go @@ -16,6 +16,20 @@ import ( "google.golang.org/protobuf/types/known/structpb" ) +var baseUrl *url.URL + +func init() { + base, ok := os.LookupEnv("APP_HOST_HTTP") + if !ok || len(base) == 0 { + panic("APP_HOST_HTTP env not set") + } + var err error + baseUrl, err = url.Parse(base) + if err != nil { + panic("Failed to get application url") + } +} + type OIDCProviderService interface { Create(context.Context, *userv3.OIDCProvider) (*userv3.OIDCProvider, error) GetByID(context.Context, *userv3.OIDCProvider) (*userv3.OIDCProvider, error) @@ -35,12 +49,8 @@ func NewOIDCProviderService(db *bun.DB) OIDCProviderService { } func generateCallbackUrl() (string, error) { - base, err := url.Parse(os.Getenv("APP_HOST_HTTP")) - if err != nil { - return "", err - } uuid := uuid.New() - return fmt.Sprintf("%s/auth/v3/sso/callback/%s", base, uuid), nil + return fmt.Sprintf("%s/auth/v3/sso/callback/%s", baseUrl.String(), uuid), nil } func (s *oidcProvider) Create(ctx context.Context, provider *userv3.OIDCProvider) (*userv3.OIDCProvider, error) { From 50d1c315c973cd216c8ddc232c2b038c678ce7e3 Mon Sep 17 00:00:00 2001 From: Akshay Gaikwad Date: Mon, 24 Jan 2022 12:49:59 +0530 Subject: [PATCH 08/12] Add OIDC providers synchronize util --- components/usermgmt/_kratos/go.mod | 21 ++++++ components/usermgmt/_kratos/go.sum | 46 +++++++++++++ components/usermgmt/_kratos/kratos.yml | 8 ++- .../_kratos/oidc_providers_example.yml | 34 ++++++++++ .../_kratos/sync_providers_from_db.go | 65 +++++++++++++++++++ 5 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 components/usermgmt/_kratos/go.mod create mode 100644 components/usermgmt/_kratos/go.sum create mode 100644 components/usermgmt/_kratos/oidc_providers_example.yml create mode 100644 components/usermgmt/_kratos/sync_providers_from_db.go 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..9b1469a --- /dev/null +++ b/components/usermgmt/_kratos/sync_providers_from_db.go @@ -0,0 +1,65 @@ +package main + +import ( + "context" + "database/sql" + "fmt" + "os" + + "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 main() { + dsn := "postgres://admindbuser:admindbpassword@localhost:5432/admindb?sslmode=disable" + sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn))) + db := bun.NewDB(sqldb, pgdialect.New()) + err := db.NewSelect().Model(&ProvidersDB).ModelTableExpr("authsrv_oidc_provider AS provider").Scan(context.Background()) + if err != nil { + fmt.Printf("failed to fetch providers from DB: ", err) + os.Exit(1) + } + + var c Config + c.Selfservice.Methods.Oidc.Config.Providers = ProvidersDB + d, err := yaml.Marshal(&c) + if err != nil { + fmt.Printf("failed to marshal: ", err) + os.Exit(1) + } + err = os.WriteFile("oidc_providers.yml", d, 0644) + if err != nil { + fmt.Printf("failed to write data: ", err) + os.Exit(1) + } +} From 3794f5e31de334c831ed6768b4c50cacae64b448 Mon Sep 17 00:00:00 2001 From: Akshay Gaikwad Date: Mon, 24 Jan 2022 13:36:40 +0530 Subject: [PATCH 09/12] Update OIDC provider sync script Trigger synchronization on PostgreSQL db notifications. --- .../_kratos/pg_oidc_provider_trigger.sql | 20 ++++++++++ .../_kratos/sync_providers_from_db.go | 40 ++++++++++++++----- 2 files changed, 49 insertions(+), 11 deletions(-) create mode 100644 components/usermgmt/_kratos/pg_oidc_provider_trigger.sql diff --git a/components/usermgmt/_kratos/pg_oidc_provider_trigger.sql b/components/usermgmt/_kratos/pg_oidc_provider_trigger.sql new file mode 100644 index 0000000..9d8d503 --- /dev/null +++ b/components/usermgmt/_kratos/pg_oidc_provider_trigger.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(); diff --git a/components/usermgmt/_kratos/sync_providers_from_db.go b/components/usermgmt/_kratos/sync_providers_from_db.go index 9b1469a..db46676 100644 --- a/components/usermgmt/_kratos/sync_providers_from_db.go +++ b/components/usermgmt/_kratos/sync_providers_from_db.go @@ -5,6 +5,7 @@ import ( "database/sql" "fmt" "os" + "time" "github.com/google/uuid" "github.com/uptrace/bun" @@ -40,26 +41,43 @@ type Config struct { var ProvidersDB []Provider -func main() { - dsn := "postgres://admindbuser:admindbpassword@localhost:5432/admindb?sslmode=disable" - sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn))) - db := bun.NewDB(sqldb, pgdialect.New()) - err := db.NewSelect().Model(&ProvidersDB).ModelTableExpr("authsrv_oidc_provider AS provider").Scan(context.Background()) +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 { - fmt.Printf("failed to fetch providers from DB: ", err) - os.Exit(1) + return fmt.Errorf("failed to fetch providers from DB: ", err) } var c Config c.Selfservice.Methods.Oidc.Config.Providers = ProvidersDB d, err := yaml.Marshal(&c) if err != nil { - fmt.Printf("failed to marshal: ", err) - os.Exit(1) + return fmt.Errorf("failed to marshal: ", err) } err = os.WriteFile("oidc_providers.yml", d, 0644) if err != nil { - fmt.Printf("failed to write data: ", err) - os.Exit(1) + return fmt.Errorf("failed to write data: ", 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", time.Now()) + if err := sync(ctx, db); err != nil { + fmt.Println(err) + } else { + fmt.Println("Synchronized successfully") + } + } + } From 1d447cf5a91eda4c9d065f78923185995fba373a Mon Sep 17 00:00:00 2001 From: Akshay Gaikwad Date: Mon, 24 Jan 2022 17:46:18 +0530 Subject: [PATCH 10/12] Remove callbackURL from oidc_provider models OIDC callback URL are Kratos url specific hence generate it dynamically. --- .../pkg/internal/models/oidc_provider.go | 1 - .../usermgmt/pkg/service/oidc_provider.go | 37 ++++--------------- 2 files changed, 8 insertions(+), 30 deletions(-) diff --git a/components/usermgmt/pkg/internal/models/oidc_provider.go b/components/usermgmt/pkg/internal/models/oidc_provider.go index 6002dc6..e24e30a 100644 --- a/components/usermgmt/pkg/internal/models/oidc_provider.go +++ b/components/usermgmt/pkg/internal/models/oidc_provider.go @@ -29,6 +29,5 @@ type OIDCProvider struct { TokenURL string `bun:"token_url"` RequestedClaims map[string]interface{} `bun:"type:jsonb"` Predefined bool `bun:"predefined,notnull"` - CallbackURL string `bun:"callback_url,notnull"` Trash bool `bun:"trash,default:false"` } diff --git a/components/usermgmt/pkg/service/oidc_provider.go b/components/usermgmt/pkg/service/oidc_provider.go index 38b6cb4..07e9fc9 100644 --- a/components/usermgmt/pkg/service/oidc_provider.go +++ b/components/usermgmt/pkg/service/oidc_provider.go @@ -3,7 +3,6 @@ package service import ( "context" "fmt" - "net/url" "os" "time" @@ -16,20 +15,6 @@ import ( "google.golang.org/protobuf/types/known/structpb" ) -var baseUrl *url.URL - -func init() { - base, ok := os.LookupEnv("APP_HOST_HTTP") - if !ok || len(base) == 0 { - panic("APP_HOST_HTTP env not set") - } - var err error - baseUrl, err = url.Parse(base) - if err != nil { - panic("Failed to get application url") - } -} - type OIDCProviderService interface { Create(context.Context, *userv3.OIDCProvider) (*userv3.OIDCProvider, error) GetByID(context.Context, *userv3.OIDCProvider) (*userv3.OIDCProvider, error) @@ -48,9 +33,9 @@ func NewOIDCProviderService(db *bun.DB) OIDCProviderService { } } -func generateCallbackUrl() (string, error) { - uuid := uuid.New() - return fmt.Sprintf("%s/auth/v3/sso/callback/%s", baseUrl.String(), uuid), nil +func generateCallbackUrl(id string) string { + base := os.Getenv("KRATOS_PUBLIC_URL") + return fmt.Sprintf("%s/self-service/methods/oidc/callback/%s", base, id) } func (s *oidcProvider) Create(ctx context.Context, provider *userv3.OIDCProvider) (*userv3.OIDCProvider, error) { @@ -65,10 +50,6 @@ func (s *oidcProvider) Create(ctx context.Context, provider *userv3.OIDCProvider return &userv3.OIDCProvider{}, fmt.Errorf("DUPLICATE NAME") } - callback, err := generateCallbackUrl() - if err != nil { - return &userv3.OIDCProvider{}, err - } entity := &models.OIDCProvider{ Name: name, CreatedAt: time.Time{}, @@ -84,9 +65,8 @@ func (s *oidcProvider) Create(ctx context.Context, provider *userv3.OIDCProvider TokenURL: provider.Spec.GetTokenUrl(), RequestedClaims: provider.Spec.GetRequestedClaims().AsMap(), Predefined: provider.Spec.GetPredefined(), - CallbackURL: callback, } - _, err = s.dao.Create(ctx, entity) + _, err := s.dao.Create(ctx, entity) if err != nil { return &userv3.OIDCProvider{}, err } @@ -112,7 +92,7 @@ func (s *oidcProvider) Create(ctx context.Context, provider *userv3.OIDCProvider TokenUrl: entity.TokenURL, RequestedClaims: rclaims, Predefined: entity.Predefined, - CallbackUrl: entity.CallbackURL, + CallbackUrl: generateCallbackUrl(entity.Id.String()), }, } return rv, nil @@ -151,7 +131,7 @@ func (s *oidcProvider) GetByID(ctx context.Context, provider *userv3.OIDCProvide TokenUrl: entity.TokenURL, RequestedClaims: rclaims, Predefined: entity.Predefined, - CallbackUrl: entity.CallbackURL, + CallbackUrl: generateCallbackUrl(entity.Id.String()), }, } return rv, nil @@ -189,7 +169,7 @@ func (s *oidcProvider) List(ctx context.Context) (*userv3.OIDCProviderList, erro TokenUrl: entity.TokenURL, RequestedClaims: rclaims, Predefined: entity.Predefined, - CallbackUrl: entity.CallbackURL, + CallbackUrl: generateCallbackUrl(entity.Id.String()), }, } result = append(result, e) @@ -245,7 +225,6 @@ func (s *oidcProvider) Update(ctx context.Context, provider *userv3.OIDCProvider TokenURL: provider.Spec.GetTokenUrl(), RequestedClaims: provider.Spec.GetRequestedClaims().AsMap(), Predefined: provider.Spec.GetPredefined(), - CallbackURL: provider.Spec.GetCallbackUrl(), } _, err = s.dao.Update(ctx, id, entity) if err != nil { @@ -273,7 +252,7 @@ func (s *oidcProvider) Update(ctx context.Context, provider *userv3.OIDCProvider TokenUrl: entity.TokenURL, RequestedClaims: rclaims, Predefined: entity.Predefined, - CallbackUrl: entity.CallbackURL, + CallbackUrl: generateCallbackUrl(entity.Id.String()), }, } return rv, nil From a03cf171d07ffd93d85a938f335001e2e2de9844 Mon Sep 17 00:00:00 2001 From: Akshay Gaikwad Date: Tue, 25 Jan 2022 13:24:56 +0530 Subject: [PATCH 11/12] Valdiate URLs in create/update OIDC provider service --- .../_kratos/sync_providers_from_db.go | 24 ++++---- .../usermgmt/pkg/service/oidc_provider.go | 59 ++++++++++++++++--- 2 files changed, 63 insertions(+), 20 deletions(-) diff --git a/components/usermgmt/_kratos/sync_providers_from_db.go b/components/usermgmt/_kratos/sync_providers_from_db.go index db46676..e060c80 100644 --- a/components/usermgmt/_kratos/sync_providers_from_db.go +++ b/components/usermgmt/_kratos/sync_providers_from_db.go @@ -17,14 +17,14 @@ import ( 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"` + 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"` + 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 { @@ -44,18 +44,18 @@ 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: ", err) + 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: ", err) + 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: ", err) + return fmt.Errorf("failed to write data: %s", err) } return nil } @@ -71,8 +71,8 @@ func main() { panic(err) } - for _ = range ln.Channel() { - fmt.Printf("%s: Received notification", time.Now()) + for range ln.Channel() { + fmt.Printf("%s: Received notification\n", time.Now()) if err := sync(ctx, db); err != nil { fmt.Println(err) } else { diff --git a/components/usermgmt/pkg/service/oidc_provider.go b/components/usermgmt/pkg/service/oidc_provider.go index 07e9fc9..7e10098 100644 --- a/components/usermgmt/pkg/service/oidc_provider.go +++ b/components/usermgmt/pkg/service/oidc_provider.go @@ -3,6 +3,7 @@ package service import ( "context" "fmt" + "net/url" "os" "time" @@ -38,6 +39,11 @@ func generateCallbackUrl(id string) string { 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() @@ -50,19 +56,37 @@ func (s *oidcProvider) Create(ctx context.Context, provider *userv3.OIDCProvider 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: provider.Spec.GetMapperUrl(), + MapperURL: mapUrl, MapperFilename: provider.Spec.GetMapperFilename(), ClientId: provider.Spec.GetClientId(), ClientSecret: provider.Spec.GetClientSecret(), Scopes: provider.Spec.GetScopes(), - IssuerURL: provider.Spec.GetIssuerUrl(), - AuthURL: provider.Spec.GetAuthUrl(), - TokenURL: provider.Spec.GetTokenUrl(), + IssuerURL: issUrl, + AuthURL: authUrl, + TokenURL: tknUrl, RequestedClaims: provider.Spec.GetRequestedClaims().AsMap(), Predefined: provider.Spec.GetPredefined(), } @@ -207,6 +231,25 @@ func (s *oidcProvider) Update(ctx context.Context, provider *userv3.OIDCProvider 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(), @@ -215,14 +258,14 @@ func (s *oidcProvider) Update(ctx context.Context, provider *userv3.OIDCProvider PartnerId: partId, ModifiedAt: time.Now(), ProviderName: provider.Spec.GetProviderName(), - MapperURL: provider.Spec.GetMapperUrl(), + MapperURL: mapUrl, MapperFilename: provider.Spec.GetMapperFilename(), ClientId: provider.Spec.GetClientId(), ClientSecret: provider.Spec.GetClientSecret(), Scopes: provider.Spec.GetScopes(), - IssuerURL: provider.Spec.GetIssuerUrl(), - AuthURL: provider.Spec.GetAuthUrl(), - TokenURL: provider.Spec.GetTokenUrl(), + IssuerURL: issUrl, + AuthURL: authUrl, + TokenURL: tknUrl, RequestedClaims: provider.Spec.GetRequestedClaims().AsMap(), Predefined: provider.Spec.GetPredefined(), } From 9b831d56b7dfc735c9614ec9e3c0efe940abf988 Mon Sep 17 00:00:00 2001 From: Akshay Gaikwad Date: Tue, 1 Feb 2022 13:39:46 +0530 Subject: [PATCH 12/12] Moved oidc provider triggers sql file to migrations --- .../migrations/admindb/pg_oidc_provider_triggers.sql | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename components/usermgmt/_kratos/pg_oidc_provider_trigger.sql => persistence/migrations/admindb/pg_oidc_provider_triggers.sql (100%) diff --git a/components/usermgmt/_kratos/pg_oidc_provider_trigger.sql b/persistence/migrations/admindb/pg_oidc_provider_triggers.sql similarity index 100% rename from components/usermgmt/_kratos/pg_oidc_provider_trigger.sql rename to persistence/migrations/admindb/pg_oidc_provider_triggers.sql