From 8464307c1e699d3497052f6697a3bc26eb376e5a Mon Sep 17 00:00:00 2001 From: Akshay Gaikwad Date: Tue, 4 Jan 2022 10:27:39 +0530 Subject: [PATCH 1/8] SAML Authentication and IDP Service Initial SAML based authentication in usermgmt component Signed-off-by: Akshay Gaikwad Update SAMLAuth middleware - Get username from request body - Validate for exiwstng session - Redirect to IDP authentication when no valid session Modification in SAML middlewares - Reduce duplicate code by new function createSAMLMiddleware Restructure saml package - Moved SAML Middlewares to middleware.go - Embed samlsp.Middleware into own struct Use EntityDAO from common and mock Idp model for testing Signed-off-by: Akshay Gaikwad Add IDP API definitions Signed-off-by: Akshay Gaikwad Add interface for IdpService Add id to UpdateIdp proto message Add metadata_url to UpdateIdp message and limit to ListIdps rpc Implement Idp Service methods Update Idp model Remove main.go and mocked idp model Generate ACS URL and SAML SP cert Change Id type in proto as well as in IDP model Update IDP model struct tags Set TimeFormat for IDP service Update generateSpCert() and generateAcsURL() Add idpServer which is wrapper around idpService idpServer is a gRPC controller. Add back-end validations for idp service --- components/usermgmt/env.example | 1 + components/usermgmt/go.mod | 8 + components/usermgmt/go.sum | 29 +- .../usermgmt/pkg/internal/models/idp.go | 34 +++ components/usermgmt/pkg/server/idp.go | 33 +++ components/usermgmt/pkg/service/idp.go | 280 ++++++++++++++++++ .../usermgmt/pkg/sso/saml/middleware.go | 167 +++++++++++ components/usermgmt/pkg/sso/saml/saml.go | 21 ++ components/usermgmt/pkg/sso/saml/saml_test.go | 1 + components/usermgmt/proto/rpc/v3/idp.proto | 22 ++ .../usermgmt/proto/types/userpb/v3/idp.proto | 64 ++++ 11 files changed, 658 insertions(+), 2 deletions(-) create mode 100644 components/usermgmt/env.example create mode 100644 components/usermgmt/pkg/internal/models/idp.go create mode 100644 components/usermgmt/pkg/server/idp.go create mode 100644 components/usermgmt/pkg/service/idp.go create mode 100644 components/usermgmt/pkg/sso/saml/middleware.go create mode 100644 components/usermgmt/pkg/sso/saml/saml.go create mode 100644 components/usermgmt/pkg/sso/saml/saml_test.go create mode 100644 components/usermgmt/proto/rpc/v3/idp.proto create mode 100644 components/usermgmt/proto/types/userpb/v3/idp.proto diff --git a/components/usermgmt/env.example b/components/usermgmt/env.example new file mode 100644 index 0000000..a2e783a --- /dev/null +++ b/components/usermgmt/env.example @@ -0,0 +1 @@ +APP_HOST_HTTP="http://localhost:8000" \ No newline at end of file diff --git a/components/usermgmt/go.mod b/components/usermgmt/go.mod index 5945c4c..fb6aff5 100644 --- a/components/usermgmt/go.mod +++ b/components/usermgmt/go.mod @@ -5,6 +5,7 @@ go 1.17 require ( github.com/RafaySystems/rcloud-base/components/adminsrv v0.0.0-unpublished github.com/RafaySystems/rcloud-base/components/common v0.0.0-unpublished + github.com/crewjam/saml v0.4.6 github.com/gogo/protobuf v1.3.2 github.com/google/uuid v1.3.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.2 @@ -21,20 +22,27 @@ require ( ) require ( + github.com/beevik/etree v1.1.0 // indirect + github.com/crewjam/httperr v0.2.0 // indirect github.com/fatih/color v1.13.0 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect + github.com/golang-jwt/jwt/v4 v4.1.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jonboulle/clockwork v0.2.2 // indirect github.com/julienschmidt/httprouter v1.3.0 // indirect github.com/klauspost/cpuid/v2 v2.0.6 // indirect github.com/magiconair/properties v1.8.5 // indirect + github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mitchellh/mapstructure v1.4.3 // indirect github.com/pelletier/go-toml v1.9.4 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/processout/grpc-go-pool v1.2.1 // indirect + github.com/russellhaering/goxmldsig v1.1.1 // indirect github.com/segmentio/asm v1.1.0 // indirect github.com/segmentio/encoding v0.3.2 // indirect github.com/speps/go-hashids v2.0.0+incompatible // indirect diff --git a/components/usermgmt/go.sum b/components/usermgmt/go.sum index fda661d..a845f90 100644 --- a/components/usermgmt/go.sum +++ b/components/usermgmt/go.sum @@ -76,6 +76,8 @@ github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= +github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -122,9 +124,14 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo= +github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4= +github.com/crewjam/saml v0.4.6 h1:XCUFPkQSJLvzyl4cW9OvpWUbRf0gE7VUpU8ZnilbeM4= +github.com/crewjam/saml v0.4.6/go.mod h1:ZBOXnNPFzB3CgOkRm7Nd6IVdkG+l/wF+0ZXLqD96t1A= 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/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= @@ -186,6 +193,8 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0= +github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= @@ -328,6 +337,7 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS 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/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= @@ -354,6 +364,9 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -365,6 +378,8 @@ github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPK github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= +github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -407,7 +422,6 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= @@ -435,6 +449,7 @@ github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -474,6 +489,11 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/russellhaering/goxmldsig v1.1.1 h1:vI0r2osGF1A9PLvsGdPUAGwEIrKa4Pj5sesSBsebIxM= +github.com/russellhaering/goxmldsig v1.1.1/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM= @@ -552,6 +572,7 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= @@ -609,6 +630,7 @@ golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 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= @@ -1057,8 +1079,9 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= @@ -1085,6 +1108,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/components/usermgmt/pkg/internal/models/idp.go b/components/usermgmt/pkg/internal/models/idp.go new file mode 100644 index 0000000..f19cc71 --- /dev/null +++ b/components/usermgmt/pkg/internal/models/idp.go @@ -0,0 +1,34 @@ +package models + +import ( + "time" + + "github.com/google/uuid" + "github.com/uptrace/bun" +) + +type Idp struct { + bun.BaseModel `bun:"table:authsrv_idp,alias:idp"` + + 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"` + + IdpName string `bun:"idp_name,notnull"` + Domain string `bun:"domain,notnull,unique"` + AcsURL string `bun:"acs_url,notnull,unique"` + OrganizationId string `bun:"organization_id,type:uuid"` + PartnerId string `bun:"partner_id,type:uuid"` + SsoURL string `bun:"sso_url"` + IdpCert string `bun:"idp_cert"` + SpCert string `bun:"sp_cert"` + SpKey string `bun:"sp_key"` + MetadataURL string `bun:"metadata_url"` + MetadataFilename string `bun:"metadata_filename"` + Metadata []byte `bun:"metadata"` + GroupAttributeName string `bun:"group_attribute_name"` + SaeEnabled bool `bun:"is_sae_enabled"` + Trash bool `bun:"trash,default:false"` +} diff --git a/components/usermgmt/pkg/server/idp.go b/components/usermgmt/pkg/server/idp.go new file mode 100644 index 0000000..5168fd5 --- /dev/null +++ b/components/usermgmt/pkg/server/idp.go @@ -0,0 +1,33 @@ +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" +) + +type idpServer struct { + service.IdpService +} + +func NewIdpServer(is service.IdpService) rpcv3.IdpServer { + return &idpServer{is} +} + +func (s *idpServer) CreateIdp(ctx context.Context, idp *userv3.NewIdp) (*userv3.Idp, error) { + return s.IdpService.CreateIdp(ctx, idp) +} + +func (s *idpServer) UpdateIdp(ctx context.Context, idp *userv3.UpdateIdp) (*userv3.Idp, error) { + return s.IdpService.UpdateIdp(ctx, idp) +} + +func (s *idpServer) GetSpConfigById(ctx context.Context, idpID *userv3.IdpID) (*userv3.SpConfig, error) { + return s.IdpService.GetSpConfigById(ctx, idpID) +} + +func (s *idpServer) ListIdps(ctx context.Context, req *userv3.ListIdpsRequest) (*userv3.ListIdpsResponse, error) { + return s.IdpService.ListIdps(ctx, req) +} diff --git a/components/usermgmt/pkg/service/idp.go b/components/usermgmt/pkg/service/idp.go new file mode 100644 index 0000000..91e0481 --- /dev/null +++ b/components/usermgmt/pkg/service/idp.go @@ -0,0 +1,280 @@ +package service + +import ( + "bytes" + "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "io/ioutil" + "math/big" + "net/url" + "os" + "time" + + "github.com/RafaySystems/rcloud-base/components/common/pkg/persistence/provider/pg" + "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" + "github.com/uptrace/bun" +) + +const TimeLayout = "2006-01-02T15:04:05.999999Z" + +type IdpService interface { + CreateIdp(context.Context, *userv3.NewIdp) (*userv3.Idp, error) + UpdateIdp(context.Context, *userv3.UpdateIdp) (*userv3.Idp, error) + GetSpConfigById(context.Context, *userv3.IdpID) (*userv3.SpConfig, error) + ListIdps(context.Context, *userv3.ListIdpsRequest) (*userv3.ListIdpsResponse, error) +} + +type idpService struct { + dao pg.EntityDAO +} + +func NewIdpService(db *bun.DB) IdpService { + return &idpService{ + dao: pg.NewEntityDAO(db), + } +} + +func generateAcsURL(baseURL string) string { + uuid := uuid.New() + acsURL := fmt.Sprintf("%s/%s/", baseURL, uuid.String()) + return acsURL +} + +// generateSpCert generates self signed certificate. Returns cert and +// private key. +func generateSpCert(host string) (string, string, error) { + // generate private key of type rsa + priv, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return "", "", err + } + privPEM := new(bytes.Buffer) + err = pem.Encode(privPEM, &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(priv), + }) + if err != nil { + return "", "", err + } + privPEMBytes, err := ioutil.ReadAll(privPEM) + if err != nil { + return "", "", err + } + + template := &x509.Certificate{ + SerialNumber: big.NewInt(1000), + Subject: pkix.Name{ + Organization: []string{"Rafay"}, + Country: []string{"US"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(30, 0, 0), + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + DNSNames: []string{host}, + } + // generate self sign certificate + cBytes, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv) + if err != nil { + return "", "", err + } + cPEM := new(bytes.Buffer) + err = pem.Encode(cPEM, &pem.Block{ + Type: "CERTIFICATE", + Bytes: cBytes, + }) + if err != nil { + return "", "", err + } + cPEMBytes, err := ioutil.ReadAll(cPEM) + if err != nil { + return "", "", err + } + + return string(cPEMBytes), string(privPEMBytes), nil +} + +func (s *idpService) CreateIdp(ctx context.Context, idp *userv3.NewIdp) (*userv3.Idp, error) { + name := idp.GetName() + domain := idp.GetDomain() + + e := &models.Idp{} + s.dao.GetByName(ctx, name, e) + if e.Name == name { + return &userv3.Idp{}, fmt.Errorf("DUPLICATE NAME") + } + s.dao.GetX(ctx, "domain", domain, e) + if e.Domain == domain { + return &userv3.Idp{}, fmt.Errorf("DUPLICATE DOMAIN") + } + + base, err := url.Parse(os.Getenv("APP_HOST_HTTP")) + if err != nil { + return &userv3.Idp{}, err + } + acsURL := generateAcsURL(base.String()) + entity := &models.Idp{ + Name: name, + IdpName: idp.GetIdpName(), + Domain: domain, + AcsURL: acsURL, + GroupAttributeName: idp.GetGroupAttributeName(), + SaeEnabled: idp.GetIsSaeEnabled(), + } + if entity.SaeEnabled { + spcert, spkey, err := generateSpCert(base.Host) + if err != nil { + return &userv3.Idp{}, err + } + entity.SpCert = spcert + entity.SpKey = spkey + } + _, err = s.dao.Create(ctx, entity) + if err != nil { + return &userv3.Idp{}, err + } + + rv := &userv3.Idp{ + Id: entity.Id.String(), + Name: entity.Name, + IdpName: entity.IdpName, + Domain: entity.Domain, + AcsUrl: entity.AcsURL, + SsoUrl: entity.SsoURL, + IdpCert: entity.IdpCert, + SpCert: entity.SpCert, + MetadataUrl: entity.MetadataURL, + MetadataFilename: entity.MetadataFilename, + IsSaeEnabled: entity.SaeEnabled, + GroupAttributeName: entity.GroupAttributeName, + OrganizationId: entity.OrganizationId, + PartnerId: entity.PartnerId, + CreatedAt: entity.CreatedAt.Format(TimeLayout), + ModifiedAt: entity.ModifiedAt.Format(TimeLayout), + } + return rv, nil +} + +func (s *idpService) UpdateIdp(ctx context.Context, new *userv3.UpdateIdp) (*userv3.Idp, error) { + id, err := uuid.Parse(new.GetId()) + if err != nil { + return &userv3.Idp{}, err + } + entity := &models.Idp{ + Id: id, + Name: new.GetName(), + ModifiedAt: time.Now(), + IdpName: new.GetIdpName(), + Domain: new.GetDomain(), + AcsURL: new.GetAcsUrl(), + MetadataURL: new.GetMetadataUrl(), + GroupAttributeName: new.GetGroupAttributeName(), + SaeEnabled: new.GetIsSaeEnabled(), + } + if entity.SaeEnabled { + base, err := url.Parse(os.Getenv("APP_HOST_HTTP")) + if err != nil { + return &userv3.Idp{}, err + } + spcert, spkey, err := generateSpCert(base.Host) + if err != nil { + return &userv3.Idp{}, err + } + entity.SpCert = spcert + entity.SpKey = spkey + } + _, err = s.dao.Update(ctx, id, entity) + if err != nil { + return &userv3.Idp{}, err + } + rv := &userv3.Idp{ + Id: entity.Id.String(), + Name: entity.Name, + IdpName: entity.IdpName, + Domain: entity.Domain, + AcsUrl: entity.AcsURL, + SsoUrl: entity.SsoURL, + IdpCert: entity.IdpCert, + SpCert: entity.SpCert, + MetadataUrl: entity.MetadataURL, + MetadataFilename: entity.MetadataFilename, + IsSaeEnabled: entity.SaeEnabled, + GroupAttributeName: entity.GroupAttributeName, + OrganizationId: entity.OrganizationId, + PartnerId: entity.PartnerId, + CreatedAt: entity.CreatedAt.Format(TimeLayout), + ModifiedAt: entity.ModifiedAt.Format(TimeLayout), + } + return rv, nil +} + +func (s *idpService) GetSpConfigById(ctx context.Context, idpID *userv3.IdpID) (*userv3.SpConfig, error) { + id, err := uuid.Parse(idpID.GetId()) + if err != nil { + return &userv3.SpConfig{}, err + } + + entity := &models.Idp{} + _, err = s.dao.GetByID(ctx, id, entity) + if err != nil { + return &userv3.SpConfig{}, err + } + if entity.Id != id { + return &userv3.SpConfig{}, fmt.Errorf("IDP ID DOES NOT EXISTS") + } + rv := &userv3.SpConfig{ + NameidFormat: "Email Address", + ConsumerBinding: "HTTP-POST", + AcsUrl: entity.AcsURL, + EntityId: entity.AcsURL, + GroupAttributeName: entity.GroupAttributeName, + SpCert: entity.SpCert, + } + return rv, nil +} + +func (s *idpService) ListIdps(ctx context.Context, req *userv3.ListIdpsRequest) (*userv3.ListIdpsResponse, error) { + entities := []*models.Idp{} + var orgID uuid.NullUUID + var parID uuid.NullUUID + s.dao.List(ctx, parID, orgID, entities) + + // Get idps only till limit + var result []*userv3.Idp + for _, entity := range entities { + e := &userv3.Idp{ + Id: entity.Id.String(), + Name: entity.Name, + IdpName: entity.IdpName, + Domain: entity.Domain, + AcsUrl: entity.AcsURL, + SsoUrl: entity.SsoURL, + IdpCert: entity.IdpCert, + SpCert: entity.SpCert, + MetadataUrl: entity.MetadataURL, + MetadataFilename: entity.MetadataFilename, + IsSaeEnabled: entity.SaeEnabled, + GroupAttributeName: entity.GroupAttributeName, + OrganizationId: entity.OrganizationId, + PartnerId: entity.PartnerId, + CreatedAt: entity.CreatedAt.Format(TimeLayout), + ModifiedAt: entity.ModifiedAt.Format(TimeLayout), + } + result = append(result, e) + } + + rv := &userv3.ListIdpsResponse{ + Count: int32(len(entities)), + Next: 0, + Previous: 0, + Result: result, + } + return rv, nil +} diff --git a/components/usermgmt/pkg/sso/saml/middleware.go b/components/usermgmt/pkg/sso/saml/middleware.go new file mode 100644 index 0000000..624fa71 --- /dev/null +++ b/components/usermgmt/pkg/sso/saml/middleware.go @@ -0,0 +1,167 @@ +package saml + +import ( + "context" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "net/http" + "net/url" + "os" + "strings" + + "github.com/RafaySystems/rcloud-base/components/usermgmt/pkg/internal/models" + "github.com/crewjam/saml" + "github.com/crewjam/saml/samlsp" +) + +func newSAMLMiddlewareFromIDP(idp models.Idp) (*SAMLMiddleware, error) { + rootURL, err := url.Parse(os.Getenv("APP_HOST_HTTP")) + if err != nil { + return nil, err + } + + var idpMetadata *saml.EntityDescriptor + if len(idp.Metadata) == 0 { + idpMetadataURL, err := url.Parse(idp.MetadataURL) + if err != nil { + return nil, err + } + idpMetadata, err = samlsp.FetchMetadata(context.Background(), http.DefaultClient, + *idpMetadataURL) + } else { + idpMetadata, err = samlsp.ParseMetadata(idp.Metadata) + if err != nil { + return nil, err + } + } + + acsURL, err := url.Parse(idp.AcsURL) + if err != nil { + return nil, err + } + + keyPair, err := tls.X509KeyPair([]byte(idp.SpCert), []byte(idp.SpKey)) + if err != nil { + return nil, err + } + keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0]) + if err != nil { + return nil, err + } + + opts := samlsp.Options{ + EntityID: "", + URL: *rootURL, + Key: keyPair.PrivateKey.(*rsa.PrivateKey), + Certificate: keyPair.Leaf, + AllowIDPInitiated: false, + DefaultRedirectURI: "/prelogin", + IDPMetadata: idpMetadata, + SignRequest: false, + } + sp := samlsp.DefaultServiceProvider(opts) + sp.AcsURL = *acsURL + m := &samlsp.Middleware{ + ServiceProvider: sp, + Binding: "", + ResponseBinding: saml.HTTPPostBinding, + OnError: samlsp.DefaultOnError, + Session: samlsp.DefaultSessionProvider(opts), + } + m.RequestTracker = samlsp.DefaultRequestTracker(opts, &m.ServiceProvider) + if opts.UseArtifactResponse { + m.ResponseBinding = saml.HTTPArtifactBinding + } + return &SAMLMiddleware{m}, nil +} + +// SAMLAuth is an authentication middleware. +func (s *SAMLService) SAMLAuth(handler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + err := r.ParseForm() + if err != nil { + http.Error(w, "failed to parse form data", http.StatusBadRequest) + return + } + username := r.PostForm.Get("username") + + if !strings.Contains(username, "@") { + http.Error(w, "Invalid email address", http.StatusBadRequest) + return + } + domain := strings.SplitN(username, "@", 2)[1] + + entity, err := s.EntityDAO.GetX(context.Background(), "domain", domain, &models.Idp{}) + if err != nil { + http.Error(w, "No idp found for domain", http.StatusInternalServerError) + return + } + idp, ok := entity.(models.Idp) + if !ok { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + + m, err := newSAMLMiddlewareFromIDP(idp) + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + + session, err := m.Session.GetSession(r) + if session != nil { + r = r.WithContext(samlsp.ContextWithSession(r.Context(), session)) + w.Write([]byte("authentiated successfully")) + return + } + if err == samlsp.ErrNoSession { + m.HandleStartAuthFlow(w, r) + return + } else { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + }) +} + +// ServeACS performs SAML Response assertions. +func (s *SAMLService) ServeACS(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + base, _ := url.Parse(os.Getenv("APP_HOST_HTTP")) + acsURL := base.ResolveReference(r.URL) + + entity, err := s.EntityDAO.GetX(context.Background(), "acs_url", acsURL.String(), &models.Idp{}) + if err != nil { + http.Error(w, "No Idp for ACS URL", http.StatusInternalServerError) + return + } + idp, ok := entity.(models.Idp) + if !ok { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + + m, err := newSAMLMiddlewareFromIDP(idp) + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + + possibleRequestIDs := []string{} + if m.ServiceProvider.AllowIDPInitiated { + possibleRequestIDs = append(possibleRequestIDs, "") + } + + trackedRequests := m.RequestTracker.GetTrackedRequests(r) + for _, tr := range trackedRequests { + possibleRequestIDs = append(possibleRequestIDs, tr.SAMLRequestID) + } + assertion, err := m.ServiceProvider.ParseResponse(r, possibleRequestIDs) + if err != nil { + m.OnError(w, r, err) + return + } + m.CreateSessionFromAssertion(w, r, assertion, m.ServiceProvider.DefaultRedirectURI) + return +} diff --git a/components/usermgmt/pkg/sso/saml/saml.go b/components/usermgmt/pkg/sso/saml/saml.go new file mode 100644 index 0000000..d4aebf0 --- /dev/null +++ b/components/usermgmt/pkg/sso/saml/saml.go @@ -0,0 +1,21 @@ +package saml + +import ( + pg "github.com/RafaySystems/rcloud-base/components/common/pkg/persistence/provider/pg" + "github.com/crewjam/saml/samlsp" + "github.com/uptrace/bun" +) + +type SAMLMiddleware struct { + *samlsp.Middleware +} + +type SAMLService struct { + EntityDAO pg.EntityDAO +} + +func NewSAMLService(db *bun.DB) *SAMLService { + return &SAMLService{ + EntityDAO: pg.NewEntityDAO(db), + } +} diff --git a/components/usermgmt/pkg/sso/saml/saml_test.go b/components/usermgmt/pkg/sso/saml/saml_test.go new file mode 100644 index 0000000..3db4e72 --- /dev/null +++ b/components/usermgmt/pkg/sso/saml/saml_test.go @@ -0,0 +1 @@ +package saml diff --git a/components/usermgmt/proto/rpc/v3/idp.proto b/components/usermgmt/proto/rpc/v3/idp.proto new file mode 100644 index 0000000..fba8349 --- /dev/null +++ b/components/usermgmt/proto/rpc/v3/idp.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; +package rafay.dev.rpc.v3; + +import "proto/types/userpb/v3/idp.proto"; + +service Idp { + // endpoint POST /auth/v1/sso/idp + rpc CreateIdp(rafay.dev.types.user.v3.NewIdp) returns (rafay.dev.types.user.v3.Idp) {}; + + // endpoint PUT /auth/v1/sso/idp + rpc UpdateIdp(rafay.dev.types.user.v3.UpdateIdp) returns (rafay.dev.types.user.v3.Idp) {}; + + // endpoint /auth/v1/sso/idp/dk351mn/spconfig/ + rpc GetSpConfigById(rafay.dev.types.user.v3.IdpID) returns (rafay.dev.types.user.v3.SpConfig) {}; + + // endpoint /auth/v1/sso/idp/?limit=1000 + rpc ListIdps(rafay.dev.types.user.v3.ListIdpsRequest) returns (rafay.dev.types.user.v3.ListIdpsResponse) {}; + + // endpooint /auth/v1/sso/idp/5m16w2y/upload_metadata/ + // file content as request payload and response is Idp +} + diff --git a/components/usermgmt/proto/types/userpb/v3/idp.proto b/components/usermgmt/proto/types/userpb/v3/idp.proto new file mode 100644 index 0000000..8e38dd7 --- /dev/null +++ b/components/usermgmt/proto/types/userpb/v3/idp.proto @@ -0,0 +1,64 @@ +syntax = "proto3"; +package rafay.dev.types.user.v3; + +message Idp { + string id = 1; + string name = 2; + string idp_name = 3; + string domain = 4; + string acs_url = 5; + string sso_url = 6; + string idp_cert = 7; + string sp_cert = 8; + string metadata_url = 9; + string metadata_filename = 10; + bool is_sae_enabled = 11; + string group_attribute_name = 12; + string organization_id = 13; + string partner_id = 14; + string created_at = 15; + string modified_at = 16; +} + +message NewIdp { + string domain = 1; + string group_attribute_name = 2; + string idp_name = 3; + bool is_sae_enabled = 4; + string name = 5; +} + +message UpdateIdp { + string id = 1; + string acs_url = 2; + string domain = 3; + string group_attribute_name = 4; + string idp_name = 5; + bool is_sae_enabled = 6; + string metadata_url = 7; + string name = 8; +} + +message IdpID { + string id = 1; +} + +message SpConfig { + string nameid_format = 1; + string consumer_binding = 2; + string acs_url = 3; + string entity_id = 4; + string group_attribute_name = 5; + string sp_cert = 6; +} + +message ListIdpsResponse { + int32 count = 1; + int32 next = 2; + int32 previous = 3; + repeated Idp result = 4; +} + +message ListIdpsRequest { + int32 limit = 1; +} From 2ef237dac57ec370636e5f158996e8868ee4529c Mon Sep 17 00:00:00 2001 From: Akshay Gaikwad Date: Mon, 17 Jan 2022 12:38:38 +0530 Subject: [PATCH 2/8] Add IdP swagger details and HTTP endpoints to RPCs Signed-off-by: Akshay Gaikwad --- .../gen/openapi/proto/rpc/v3/idp.swagger.json | 390 ++++++++++++++++++ .../proto/types/userpb/v3/idp.swagger.json | 43 ++ components/usermgmt/proto/rpc/v3/idp.proto | 90 +++- 3 files changed, 514 insertions(+), 9 deletions(-) create mode 100644 components/usermgmt/gen/openapi/proto/rpc/v3/idp.swagger.json create mode 100644 components/usermgmt/gen/openapi/proto/types/userpb/v3/idp.swagger.json diff --git a/components/usermgmt/gen/openapi/proto/rpc/v3/idp.swagger.json b/components/usermgmt/gen/openapi/proto/rpc/v3/idp.swagger.json new file mode 100644 index 0000000..feb0a92 --- /dev/null +++ b/components/usermgmt/gen/openapi/proto/rpc/v3/idp.swagger.json @@ -0,0 +1,390 @@ +{ + "swagger": "2.0", + "info": { + "title": "Identity Provider (IdP) Service", + "version": "2.0", + "contact": { + "name": "Rafay Dev" + } + }, + "tags": [ + { + "name": "Idp" + } + ], + "schemes": [ + "https" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/auth/v3/sso/idp": { + "get": { + "operationId": "Idp_ListIdps", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v3ListIdpsResponse" + } + }, + "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": "limit", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + } + ], + "tags": [ + "Idp" + ] + }, + "post": { + "operationId": "Idp_CreateIdp", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v3Idp" + } + }, + "201": { + "description": "Returned when idp 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/v3NewIdp" + } + } + ], + "tags": [ + "Idp" + ] + } + }, + "/auth/v3/sso/idp/{id}": { + "put": { + "operationId": "Idp_UpdateIdp", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v3Idp" + } + }, + "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": "id", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "acsUrl": { + "type": "string" + }, + "domain": { + "type": "string" + }, + "groupAttributeName": { + "type": "string" + }, + "idpName": { + "type": "string" + }, + "isSaeEnabled": { + "type": "boolean" + }, + "metadataUrl": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + } + ], + "tags": [ + "Idp" + ] + } + }, + "/auth/v3/sso/idp/{id}/spconfig": { + "get": { + "operationId": "Idp_GetSpConfigById", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v3SpConfig" + } + }, + "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": "id", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "Idp" + ] + } + } + }, + "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": {} + }, + "v3Idp": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "idpName": { + "type": "string" + }, + "domain": { + "type": "string" + }, + "acsUrl": { + "type": "string" + }, + "ssoUrl": { + "type": "string" + }, + "idpCert": { + "type": "string" + }, + "spCert": { + "type": "string" + }, + "metadataUrl": { + "type": "string" + }, + "metadataFilename": { + "type": "string" + }, + "isSaeEnabled": { + "type": "boolean" + }, + "groupAttributeName": { + "type": "string" + }, + "organizationId": { + "type": "string" + }, + "partnerId": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "modifiedAt": { + "type": "string" + } + } + }, + "v3ListIdpsResponse": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "format": "int32" + }, + "next": { + "type": "integer", + "format": "int32" + }, + "previous": { + "type": "integer", + "format": "int32" + }, + "result": { + "type": "array", + "items": { + "$ref": "#/definitions/v3Idp" + } + } + } + }, + "v3NewIdp": { + "type": "object", + "properties": { + "domain": { + "type": "string" + }, + "groupAttributeName": { + "type": "string" + }, + "idpName": { + "type": "string" + }, + "isSaeEnabled": { + "type": "boolean" + }, + "name": { + "type": "string" + } + } + }, + "v3SpConfig": { + "type": "object", + "properties": { + "nameidFormat": { + "type": "string" + }, + "consumerBinding": { + "type": "string" + }, + "acsUrl": { + "type": "string" + }, + "entityId": { + "type": "string" + }, + "groupAttributeName": { + "type": "string" + }, + "spCert": { + "type": "string" + } + } + } + }, + "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/idp.swagger.json b/components/usermgmt/gen/openapi/proto/types/userpb/v3/idp.swagger.json new file mode 100644 index 0000000..6a8e370 --- /dev/null +++ b/components/usermgmt/gen/openapi/proto/types/userpb/v3/idp.swagger.json @@ -0,0 +1,43 @@ +{ + "swagger": "2.0", + "info": { + "title": "proto/types/userpb/v3/idp.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/idp.proto b/components/usermgmt/proto/rpc/v3/idp.proto index fba8349..bcda8fd 100644 --- a/components/usermgmt/proto/rpc/v3/idp.proto +++ b/components/usermgmt/proto/rpc/v3/idp.proto @@ -1,22 +1,94 @@ syntax = "proto3"; package rafay.dev.rpc.v3; +import "google/api/annotations.proto"; +import "protoc-gen-openapiv2/options/annotations.proto"; import "proto/types/userpb/v3/idp.proto"; +option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { + info : { + title : "Identity Provider (IdP) Service" + version : "2.0" + contact : {name : "Rafay Dev"} + } + schemes : HTTPS + consumes : "application/json" + produces : "application/json" + 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 Idp { - // endpoint POST /auth/v1/sso/idp - rpc CreateIdp(rafay.dev.types.user.v3.NewIdp) returns (rafay.dev.types.user.v3.Idp) {}; + rpc CreateIdp(rafay.dev.types.user.v3.NewIdp) returns (rafay.dev.types.user.v3.Idp) { + option (google.api.http) = { + post : "/auth/v3/sso/idp" + body : "*" + }; - // endpoint PUT /auth/v1/sso/idp - rpc UpdateIdp(rafay.dev.types.user.v3.UpdateIdp) returns (rafay.dev.types.user.v3.Idp) {}; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + responses : { + key : "201" + value : {description : "Returned when idp is created successfully."} + } + }; + }; - // endpoint /auth/v1/sso/idp/dk351mn/spconfig/ - rpc GetSpConfigById(rafay.dev.types.user.v3.IdpID) returns (rafay.dev.types.user.v3.SpConfig) {}; + rpc UpdateIdp(rafay.dev.types.user.v3.UpdateIdp) returns (rafay.dev.types.user.v3.Idp) { + option (google.api.http) = { + put : "/auth/v3/sso/idp/{id}" + body : "*" + }; + }; - // endpoint /auth/v1/sso/idp/?limit=1000 - rpc ListIdps(rafay.dev.types.user.v3.ListIdpsRequest) returns (rafay.dev.types.user.v3.ListIdpsResponse) {}; + rpc GetSpConfigById(rafay.dev.types.user.v3.IdpID) returns (rafay.dev.types.user.v3.SpConfig) { + option (google.api.http) = { + get : "/auth/v3/sso/idp/{id}/spconfig" + }; + }; - // endpooint /auth/v1/sso/idp/5m16w2y/upload_metadata/ + rpc ListIdps(rafay.dev.types.user.v3.ListIdpsRequest) returns (rafay.dev.types.user.v3.ListIdpsResponse) { + option (google.api.http) = { + get : "/auth/v3/sso/idp" + }; + }; + + // DELETE /auth/v1/sso/idp/{id}/ + // Delete IdP + + // endpooint /auth/v1/sso/idp/{id}/upload_metadata/ // file content as request payload and response is Idp } From d794603d7fa865307f7c48abd7a0bcf087758d65 Mon Sep 17 00:00:00 2001 From: Akshay Gaikwad Date: Mon, 17 Jan 2022 13:08:46 +0530 Subject: [PATCH 3/8] Add DeleteIdp rpc method Signed-off-by: Akshay Gaikwad --- .../gen/openapi/proto/rpc/v3/idp.swagger.json | 44 +++++++++++++++++++ components/usermgmt/pkg/server/idp.go | 5 +++ components/usermgmt/pkg/service/idp.go | 16 +++++++ components/usermgmt/proto/rpc/v3/idp.proto | 16 ++++++- 4 files changed, 80 insertions(+), 1 deletion(-) diff --git a/components/usermgmt/gen/openapi/proto/rpc/v3/idp.swagger.json b/components/usermgmt/gen/openapi/proto/rpc/v3/idp.swagger.json index feb0a92..75e53a7 100644 --- a/components/usermgmt/gen/openapi/proto/rpc/v3/idp.swagger.json +++ b/components/usermgmt/gen/openapi/proto/rpc/v3/idp.swagger.json @@ -110,6 +110,50 @@ } }, "/auth/v3/sso/idp/{id}": { + "delete": { + "summary": "DELETE /auth/v1/sso/idp/{id}/", + "operationId": "Idp_DeleteIdp", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "properties": {} + } + }, + "204": { + "description": "Returned when idp 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": "id", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "Idp" + ] + }, "put": { "operationId": "Idp_UpdateIdp", "responses": { diff --git a/components/usermgmt/pkg/server/idp.go b/components/usermgmt/pkg/server/idp.go index 5168fd5..45b6103 100644 --- a/components/usermgmt/pkg/server/idp.go +++ b/components/usermgmt/pkg/server/idp.go @@ -6,6 +6,7 @@ import ( "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 idpServer struct { @@ -31,3 +32,7 @@ func (s *idpServer) GetSpConfigById(ctx context.Context, idpID *userv3.IdpID) (* func (s *idpServer) ListIdps(ctx context.Context, req *userv3.ListIdpsRequest) (*userv3.ListIdpsResponse, error) { return s.IdpService.ListIdps(ctx, req) } + +func (s *idpServer) DeleteIdp(ctx context.Context, idpID *userv3.IdpID) (*emptypb.Empty, error) { + return s.IdpService.DeleteIdp(ctx, idpID) +} diff --git a/components/usermgmt/pkg/service/idp.go b/components/usermgmt/pkg/service/idp.go index 91e0481..837f1f7 100644 --- a/components/usermgmt/pkg/service/idp.go +++ b/components/usermgmt/pkg/service/idp.go @@ -20,6 +20,7 @@ import ( userv3 "github.com/RafaySystems/rcloud-base/components/usermgmt/proto/types/userpb/v3" "github.com/google/uuid" "github.com/uptrace/bun" + "google.golang.org/protobuf/types/known/emptypb" ) const TimeLayout = "2006-01-02T15:04:05.999999Z" @@ -29,6 +30,7 @@ type IdpService interface { UpdateIdp(context.Context, *userv3.UpdateIdp) (*userv3.Idp, error) GetSpConfigById(context.Context, *userv3.IdpID) (*userv3.SpConfig, error) ListIdps(context.Context, *userv3.ListIdpsRequest) (*userv3.ListIdpsResponse, error) + DeleteIdp(context.Context, *userv3.IdpID) (*emptypb.Empty, error) } type idpService struct { @@ -278,3 +280,17 @@ func (s *idpService) ListIdps(ctx context.Context, req *userv3.ListIdpsRequest) } return rv, nil } + +func (s *idpService) DeleteIdp(ctx context.Context, idpID *userv3.IdpID) (*emptypb.Empty, error) { + id, err := uuid.Parse(idpID.GetId()) + if err != nil { + return &emptypb.Empty{}, err + } + + entity := &models.Idp{} + err = s.dao.Delete(ctx, id, entity) + if err != nil { + return &emptypb.Empty{}, err + } + return &emptypb.Empty{}, nil +} diff --git a/components/usermgmt/proto/rpc/v3/idp.proto b/components/usermgmt/proto/rpc/v3/idp.proto index bcda8fd..cf9234d 100644 --- a/components/usermgmt/proto/rpc/v3/idp.proto +++ b/components/usermgmt/proto/rpc/v3/idp.proto @@ -4,6 +4,7 @@ package rafay.dev.rpc.v3; import "google/api/annotations.proto"; import "protoc-gen-openapiv2/options/annotations.proto"; import "proto/types/userpb/v3/idp.proto"; +import "google/protobuf/empty.proto"; option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { info : { @@ -86,7 +87,20 @@ service Idp { }; // DELETE /auth/v1/sso/idp/{id}/ - // Delete IdP + rpc DeleteIdp(rafay.dev.types.user.v3.IdpID) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete : "/auth/v3/sso/idp/{id}" + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + responses : { + key : "204" + value : { + description : "Returned when idp is deleted successfully." + } + } + }; + }; // endpooint /auth/v1/sso/idp/{id}/upload_metadata/ // file content as request payload and response is Idp From c1ba133383144ae1aa76a0449de5a50becffe51a Mon Sep 17 00:00:00 2001 From: Akshay Gaikwad Date: Tue, 18 Jan 2022 18:07:17 +0530 Subject: [PATCH 4/8] Register IdpService in main.go and minor changes in Idp model --- components/usermgmt/main.go | 5 ++++ .../usermgmt/pkg/internal/models/idp.go | 30 +++++++++---------- components/usermgmt/pkg/service/idp.go | 18 ++++++----- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/components/usermgmt/main.go b/components/usermgmt/main.go index 972c445..22754e3 100644 --- a/components/usermgmt/main.go +++ b/components/usermgmt/main.go @@ -61,6 +61,7 @@ var ( us service.UserService gs service.GroupService rs service.RoleService + is service.IdpService dev bool _log = logv2.GetLogger() authPool authv3.AuthPool @@ -122,6 +123,7 @@ func setup() { us = service.NewUserService(kc, db) gs = service.NewGroupService(db) rs = service.NewRoleService(db) + is = service.NewIdpService(db) _log.Infow("usermgmt setup complete") } @@ -155,6 +157,7 @@ func runAPI(wg *sync.WaitGroup, ctx context.Context) { pbrpcv3.RegisterUserHandlerFromEndpoint, pbrpcv3.RegisterGroupHandlerFromEndpoint, pbrpcv3.RegisterRoleHandlerFromEndpoint, + pbrpcv3.RegisterIdpHandlerFromEndpoint, ) if err != nil { _log.Fatalw("unable to create gateway", "error", err) @@ -188,6 +191,7 @@ func runRPC(wg *sync.WaitGroup, ctx context.Context) { userServer := server.NewUserServer(us) groupServer := server.NewGroupServer(gs) roleServer := server.NewRoleServer(rs) + idpServer := server.NewIdpServer(is) l, err := net.Listen("tcp", fmt.Sprintf(":%d", rpcPort)) if err != nil { @@ -224,6 +228,7 @@ func runRPC(wg *sync.WaitGroup, ctx context.Context) { rpcv3.RegisterUserServer(s, userServer) rpcv3.RegisterGroupServer(s, groupServer) rpcv3.RegisterRoleServer(s, roleServer) + rpcv3.RegisterIdpServer(s, idpServer) _log.Infow("starting rpc server", "port", rpcPort) err = s.Serve(l) diff --git a/components/usermgmt/pkg/internal/models/idp.go b/components/usermgmt/pkg/internal/models/idp.go index f19cc71..01d1df3 100644 --- a/components/usermgmt/pkg/internal/models/idp.go +++ b/components/usermgmt/pkg/internal/models/idp.go @@ -16,19 +16,19 @@ type Idp struct { CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp"` ModifiedAt time.Time `bun:"modified_at,notnull,default:current_timestamp"` - IdpName string `bun:"idp_name,notnull"` - Domain string `bun:"domain,notnull,unique"` - AcsURL string `bun:"acs_url,notnull,unique"` - OrganizationId string `bun:"organization_id,type:uuid"` - PartnerId string `bun:"partner_id,type:uuid"` - SsoURL string `bun:"sso_url"` - IdpCert string `bun:"idp_cert"` - SpCert string `bun:"sp_cert"` - SpKey string `bun:"sp_key"` - MetadataURL string `bun:"metadata_url"` - MetadataFilename string `bun:"metadata_filename"` - Metadata []byte `bun:"metadata"` - GroupAttributeName string `bun:"group_attribute_name"` - SaeEnabled bool `bun:"is_sae_enabled"` - Trash bool `bun:"trash,default:false"` + IdpName string `bun:"idp_name,notnull"` + Domain string `bun:"domain,notnull,unique"` + AcsURL string `bun:"acs_url,notnull,unique"` + OrganizationId uuid.UUID `bun:"organization_id,type:uuid"` + PartnerId uuid.UUID `bun:"partner_id,type:uuid"` + SsoURL string `bun:"sso_url"` + IdpCert string `bun:"idp_cert"` + SpCert string `bun:"sp_cert"` + SpKey string `bun:"sp_key"` + MetadataURL string `bun:"metadata_url"` + MetadataFilename string `bun:"metadata_filename"` + Metadata []byte `bun:"metadata"` + GroupAttributeName string `bun:"group_attribute_name"` + SaeEnabled bool `bun:"is_sae_enabled"` + Trash bool `bun:"trash,default:false"` } diff --git a/components/usermgmt/pkg/service/idp.go b/components/usermgmt/pkg/service/idp.go index 837f1f7..c98d7f6 100644 --- a/components/usermgmt/pkg/service/idp.go +++ b/components/usermgmt/pkg/service/idp.go @@ -129,6 +129,8 @@ func (s *idpService) CreateIdp(ctx context.Context, idp *userv3.NewIdp) (*userv3 AcsURL: acsURL, GroupAttributeName: idp.GetGroupAttributeName(), SaeEnabled: idp.GetIsSaeEnabled(), + CreatedAt: time.Now(), + ModifiedAt: time.Now(), } if entity.SaeEnabled { spcert, spkey, err := generateSpCert(base.Host) @@ -156,8 +158,8 @@ func (s *idpService) CreateIdp(ctx context.Context, idp *userv3.NewIdp) (*userv3 MetadataFilename: entity.MetadataFilename, IsSaeEnabled: entity.SaeEnabled, GroupAttributeName: entity.GroupAttributeName, - OrganizationId: entity.OrganizationId, - PartnerId: entity.PartnerId, + OrganizationId: entity.OrganizationId.String(), + PartnerId: entity.PartnerId.String(), CreatedAt: entity.CreatedAt.Format(TimeLayout), ModifiedAt: entity.ModifiedAt.Format(TimeLayout), } @@ -209,8 +211,8 @@ func (s *idpService) UpdateIdp(ctx context.Context, new *userv3.UpdateIdp) (*use MetadataFilename: entity.MetadataFilename, IsSaeEnabled: entity.SaeEnabled, GroupAttributeName: entity.GroupAttributeName, - OrganizationId: entity.OrganizationId, - PartnerId: entity.PartnerId, + OrganizationId: entity.OrganizationId.String(), + PartnerId: entity.PartnerId.String(), CreatedAt: entity.CreatedAt.Format(TimeLayout), ModifiedAt: entity.ModifiedAt.Format(TimeLayout), } @@ -243,10 +245,10 @@ func (s *idpService) GetSpConfigById(ctx context.Context, idpID *userv3.IdpID) ( } func (s *idpService) ListIdps(ctx context.Context, req *userv3.ListIdpsRequest) (*userv3.ListIdpsResponse, error) { - entities := []*models.Idp{} + var entities []models.Idp var orgID uuid.NullUUID var parID uuid.NullUUID - s.dao.List(ctx, parID, orgID, entities) + s.dao.List(ctx, parID, orgID, &entities) // Get idps only till limit var result []*userv3.Idp @@ -264,8 +266,8 @@ func (s *idpService) ListIdps(ctx context.Context, req *userv3.ListIdpsRequest) MetadataFilename: entity.MetadataFilename, IsSaeEnabled: entity.SaeEnabled, GroupAttributeName: entity.GroupAttributeName, - OrganizationId: entity.OrganizationId, - PartnerId: entity.PartnerId, + OrganizationId: entity.OrganizationId.String(), + PartnerId: entity.PartnerId.String(), CreatedAt: entity.CreatedAt.Format(TimeLayout), ModifiedAt: entity.ModifiedAt.Format(TimeLayout), } From ad3cbc10d3fe9d2f382716a33cfcd36bb63dc593 Mon Sep 17 00:00:00 2001 From: Akshay Gaikwad Date: Wed, 19 Jan 2022 12:38:09 +0530 Subject: [PATCH 5/8] Convert Idp API spec to GVK style --- .../gen/openapi/proto/rpc/v3/idp.swagger.json | 763 ++++++++++++++---- components/usermgmt/proto/rpc/v3/idp.proto | 33 +- .../usermgmt/proto/types/userpb/v3/idp.proto | 143 ++-- 3 files changed, 710 insertions(+), 229 deletions(-) diff --git a/components/usermgmt/gen/openapi/proto/rpc/v3/idp.swagger.json b/components/usermgmt/gen/openapi/proto/rpc/v3/idp.swagger.json index 75e53a7..25b091b 100644 --- a/components/usermgmt/gen/openapi/proto/rpc/v3/idp.swagger.json +++ b/components/usermgmt/gen/openapi/proto/rpc/v3/idp.swagger.json @@ -16,10 +16,12 @@ "https" ], "consumes": [ - "application/json" + "application/json", + "application/yaml" ], "produces": [ - "application/json" + "application/json", + "application/yaml" ], "paths": { "/auth/v3/sso/idp": { @@ -29,7 +31,7 @@ "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v3ListIdpsResponse" + "$ref": "#/definitions/v3IdpList" } }, "403": { @@ -50,15 +52,6 @@ } } }, - "parameters": [ - { - "name": "limit", - "in": "query", - "required": false, - "type": "integer", - "format": "int32" - } - ], "tags": [ "Idp" ] @@ -100,7 +93,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/v3NewIdp" + "$ref": "#/definitions/v3Idp" } } ], @@ -109,9 +102,219 @@ ] } }, - "/auth/v3/sso/idp/{id}": { + "/auth/v3/sso/idp/{metadata.id}": { + "get": { + "operationId": "Idp_GetIdp", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v3Idp" + } + }, + "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 idp resource", + "in": "query", + "required": false, + "type": "string", + "default": "usermgmt.k8smgmt.io/v3" + }, + { + "name": "kind", + "description": "Kind. Kind of the idp resource", + "in": "query", + "required": false, + "type": "string", + "default": "Idp" + }, + { + "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.idpName", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "spec.domain", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "spec.acsUrl", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "spec.ssoUrl", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "spec.idpCert", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "spec.spCert", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "spec.metadataUrl", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "spec.metadataFilename", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "spec.saeEnabled", + "in": "query", + "required": false, + "type": "boolean" + }, + { + "name": "spec.groupAttributeName", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "spec.nameIdFormat", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "spec.consumerBinding", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "spec.spEntityId", + "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": [ + "Idp" + ] + }, "delete": { - "summary": "DELETE /auth/v1/sso/idp/{id}/", "operationId": "Idp_DeleteIdp", "responses": { "200": { @@ -144,10 +347,182 @@ }, "parameters": [ { - "name": "id", + "name": "metadata.id", "in": "path", "required": true, "type": "string" + }, + { + "name": "apiVersion", + "description": "API Version. API Version of the idp resource", + "in": "query", + "required": false, + "type": "string", + "default": "usermgmt.k8smgmt.io/v3" + }, + { + "name": "kind", + "description": "Kind. Kind of the idp resource", + "in": "query", + "required": false, + "type": "string", + "default": "Idp" + }, + { + "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.idpName", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "spec.domain", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "spec.acsUrl", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "spec.ssoUrl", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "spec.idpCert", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "spec.spCert", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "spec.metadataUrl", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "spec.metadataFilename", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "spec.saeEnabled", + "in": "query", + "required": false, + "type": "boolean" + }, + { + "name": "spec.groupAttributeName", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "spec.nameIdFormat", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "spec.consumerBinding", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "spec.spEntityId", + "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": [ @@ -183,7 +558,7 @@ }, "parameters": [ { - "name": "id", + "name": "metadata.id", "in": "path", "required": true, "type": "string" @@ -193,30 +568,7 @@ "in": "body", "required": true, "schema": { - "type": "object", - "properties": { - "acsUrl": { - "type": "string" - }, - "domain": { - "type": "string" - }, - "groupAttributeName": { - "type": "string" - }, - "idpName": { - "type": "string" - }, - "isSaeEnabled": { - "type": "boolean" - }, - "metadataUrl": { - "type": "string" - }, - "name": { - "type": "string" - } - } + "$ref": "#/definitions/v3Idp" } } ], @@ -224,50 +576,48 @@ "Idp" ] } - }, - "/auth/v3/sso/idp/{id}/spconfig": { - "get": { - "operationId": "Idp_GetSpConfigById", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/v3SpConfig" - } - }, - "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": "id", - "in": "path", - "required": true, - "type": "string" - } - ], - "tags": [ - "Idp" - ] - } } }, "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": { @@ -295,15 +645,98 @@ }, "additionalProperties": {} }, + "v3ConditionStatus": { + "type": "string", + "enum": [ + "StatusNotSet", + "StatusSubmitted", + "StatusOK", + "StatusFailed" + ], + "default": "StatusNotSet", + "title": "$title: ConditionStatus\n$description: status of a condition for a resource" + }, "v3Idp": { "type": "object", "properties": { - "id": { - "type": "string" + "apiVersion": { + "type": "string", + "default": "usermgmt.k8smgmt.io/v3", + "description": "API Version of the idp resource", + "title": "API Version" }, - "name": { - "type": "string" + "kind": { + "type": "string", + "default": "Idp", + "description": "Kind of the idp resource", + "title": "Kind" }, + "metadata": { + "$ref": "#/definitions/v3Metadata", + "description": "Metadata of the idp resource", + "title": "Metadata" + }, + "spec": { + "$ref": "#/definitions/v3IdpSpec", + "description": "Spec of the idp resource", + "title": "Spec" + }, + "status": { + "$ref": "#/definitions/commonv3Status", + "description": "Status of the resource", + "title": "Status", + "readOnly": true + } + }, + "description": "Idp", + "title": "Idp", + "required": [ + "apiVersion", + "kind", + "metadata", + "spec" + ] + }, + "v3IdpList": { + "type": "object", + "properties": { + "apiVersion": { + "type": "string", + "default": "usermgmt.k8smgmt.io/v3", + "description": "API Version of the idp list resource", + "title": "API Version", + "readOnly": true + }, + "kind": { + "type": "string", + "default": "IdpList", + "description": "Kind of the idp list resource", + "title": "Kind", + "readOnly": true + }, + "metadata": { + "$ref": "#/definitions/v3ListMetadata", + "description": "Metadata of the idp list resource", + "title": "Metadata", + "readOnly": true + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/v3Idp" + }, + "description": "List of the idp resources", + "title": "Items", + "readOnly": true + } + }, + "description": "idp list", + "title": "IdpList", + "readOnly": true + }, + "v3IdpSpec": { + "type": "object", + "properties": { "idpName": { "type": "string" }, @@ -328,91 +761,105 @@ "metadataFilename": { "type": "string" }, - "isSaeEnabled": { + "saeEnabled": { "type": "boolean" }, "groupAttributeName": { "type": "string" }, - "organizationId": { - "type": "string" - }, - "partnerId": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "modifiedAt": { - "type": "string" - } - } - }, - "v3ListIdpsResponse": { - "type": "object", - "properties": { - "count": { - "type": "integer", - "format": "int32" - }, - "next": { - "type": "integer", - "format": "int32" - }, - "previous": { - "type": "integer", - "format": "int32" - }, - "result": { - "type": "array", - "items": { - "$ref": "#/definitions/v3Idp" - } - } - } - }, - "v3NewIdp": { - "type": "object", - "properties": { - "domain": { - "type": "string" - }, - "groupAttributeName": { - "type": "string" - }, - "idpName": { - "type": "string" - }, - "isSaeEnabled": { - "type": "boolean" - }, - "name": { - "type": "string" - } - } - }, - "v3SpConfig": { - "type": "object", - "properties": { - "nameidFormat": { + "nameIdFormat": { "type": "string" }, "consumerBinding": { "type": "string" }, - "acsUrl": { - "type": "string" - }, - "entityId": { - "type": "string" - }, - "groupAttributeName": { - "type": "string" - }, - "spCert": { + "spEntityId": { "type": "string" } } + }, + "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" + ] } }, "securityDefinitions": { diff --git a/components/usermgmt/proto/rpc/v3/idp.proto b/components/usermgmt/proto/rpc/v3/idp.proto index cf9234d..8ebe111 100644 --- a/components/usermgmt/proto/rpc/v3/idp.proto +++ b/components/usermgmt/proto/rpc/v3/idp.proto @@ -14,7 +14,9 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { } schemes : HTTPS consumes : "application/json" + consumes : "application/yaml" produces : "application/json" + produces : "application/yaml" security_definitions : { security : { key : "BasicAuth" @@ -53,7 +55,7 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { service Idp { - rpc CreateIdp(rafay.dev.types.user.v3.NewIdp) returns (rafay.dev.types.user.v3.Idp) { + rpc CreateIdp(rafay.dev.types.user.v3.Idp) returns (rafay.dev.types.user.v3.Idp) { option (google.api.http) = { post : "/auth/v3/sso/idp" body : "*" @@ -67,29 +69,28 @@ service Idp { }; }; - rpc UpdateIdp(rafay.dev.types.user.v3.UpdateIdp) returns (rafay.dev.types.user.v3.Idp) { + rpc GetIdp(rafay.dev.types.user.v3.Idp) returns (rafay.dev.types.user.v3.Idp) { option (google.api.http) = { - put : "/auth/v3/sso/idp/{id}" - body : "*" + get : "/auth/v3/sso/idp/{metadata.id}" }; }; - rpc GetSpConfigById(rafay.dev.types.user.v3.IdpID) returns (rafay.dev.types.user.v3.SpConfig) { - option (google.api.http) = { - get : "/auth/v3/sso/idp/{id}/spconfig" - }; - }; - - rpc ListIdps(rafay.dev.types.user.v3.ListIdpsRequest) returns (rafay.dev.types.user.v3.ListIdpsResponse) { + rpc ListIdps(google.protobuf.Empty) returns (rafay.dev.types.user.v3.IdpList) { option (google.api.http) = { get : "/auth/v3/sso/idp" }; }; - // DELETE /auth/v1/sso/idp/{id}/ - rpc DeleteIdp(rafay.dev.types.user.v3.IdpID) returns (google.protobuf.Empty) { + rpc UpdateIdp(rafay.dev.types.user.v3.Idp) returns (rafay.dev.types.user.v3.Idp) { option (google.api.http) = { - delete : "/auth/v3/sso/idp/{id}" + put : "/auth/v3/sso/idp/{metadata.id}" + body : "*" + }; + }; + + rpc DeleteIdp(rafay.dev.types.user.v3.Idp) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete : "/auth/v3/sso/idp/{metadata.id}" }; option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { @@ -101,8 +102,8 @@ service Idp { } }; }; - - // endpooint /auth/v1/sso/idp/{id}/upload_metadata/ + + // TODO: endpooint /auth/v1/sso/idp/{id}/upload_metadata/ // file content as request payload and response is Idp } diff --git a/components/usermgmt/proto/types/userpb/v3/idp.proto b/components/usermgmt/proto/types/userpb/v3/idp.proto index 8e38dd7..37bbef1 100644 --- a/components/usermgmt/proto/types/userpb/v3/idp.proto +++ b/components/usermgmt/proto/types/userpb/v3/idp.proto @@ -1,64 +1,97 @@ syntax = "proto3"; package rafay.dev.types.user.v3; +import "proto/types/commonpb/v3/common.proto"; +import "protoc-gen-openapiv2/options/annotations.proto"; + message Idp { - string id = 1; - string name = 2; - string idp_name = 3; - string domain = 4; - string acs_url = 5; - string sso_url = 6; - string idp_cert = 7; - string sp_cert = 8; - string metadata_url = 9; - string metadata_filename = 10; - bool is_sae_enabled = 11; - string group_attribute_name = 12; - string organization_id = 13; - string partner_id = 14; - string created_at = 15; - string modified_at = 16; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema : { + title : "Idp" + description : "Idp" + required : [ "apiVersion", "kind", "metadata", "spec" ] + } + }; + + string apiVersion = 1 + [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + title : "API Version", + description : "API Version of the idp resource" + default : "usermgmt.k8smgmt.io/v3" + } ]; + string kind = 2 + [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + title : "Kind", + description : "Kind of the idp resource" + default : "Idp" + } ]; + rafay.dev.types.common.v3.Metadata metadata = 3 + [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + title : "Metadata", + description : "Metadata of the idp resource" + } ]; + IdpSpec spec = 4 + [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + title : "Spec", + description : "Spec of the idp 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 NewIdp { - string domain = 1; - string group_attribute_name = 2; - string idp_name = 3; - bool is_sae_enabled = 4; - string name = 5; +message IdpSpec { + string idpName = 1; + string domain = 2; + string acsUrl = 3; + string ssoUrl = 4; + string idpCert = 5; + string spCert = 6; + string metadataUrl = 7; + string metadataFilename = 8; + bool saeEnabled = 9; + string groupAttributeName = 10; + string nameIdFormat = 11; + string consumerBinding = 12; + string spEntityId = 13; } -message UpdateIdp { - string id = 1; - string acs_url = 2; - string domain = 3; - string group_attribute_name = 4; - string idp_name = 5; - bool is_sae_enabled = 6; - string metadata_url = 7; - string name = 8; -} - -message IdpID { - string id = 1; -} - -message SpConfig { - string nameid_format = 1; - string consumer_binding = 2; - string acs_url = 3; - string entity_id = 4; - string group_attribute_name = 5; - string sp_cert = 6; -} - -message ListIdpsResponse { - int32 count = 1; - int32 next = 2; - int32 previous = 3; - repeated Idp result = 4; -} - -message ListIdpsRequest { - int32 limit = 1; +message IdpList { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema : { + title : "IdpList" + description : "idp list" + read_only : true + } + }; + string apiVersion = 1 + [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + title : "API Version", + description : "API Version of the idp 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 idp list resource" + default : "IdpList" + 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 idp list resource" + read_only : true + } ]; + repeated Idp items = 4 + [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + title : "Items", + description : "List of the idp resources" + read_only : true + } ]; } From ed0390cb8c8be52dcc98f0b95e268241975cb661 Mon Sep 17 00:00:00 2001 From: Akshay Gaikwad Date: Wed, 19 Jan 2022 13:47:43 +0530 Subject: [PATCH 6/8] Update Idp service according to GVK spec --- components/usermgmt/pkg/server/idp.go | 24 +-- components/usermgmt/pkg/service/idp.go | 261 ++++++++++++++----------- 2 files changed, 163 insertions(+), 122 deletions(-) diff --git a/components/usermgmt/pkg/server/idp.go b/components/usermgmt/pkg/server/idp.go index 45b6103..83a4124 100644 --- a/components/usermgmt/pkg/server/idp.go +++ b/components/usermgmt/pkg/server/idp.go @@ -17,22 +17,22 @@ func NewIdpServer(is service.IdpService) rpcv3.IdpServer { return &idpServer{is} } -func (s *idpServer) CreateIdp(ctx context.Context, idp *userv3.NewIdp) (*userv3.Idp, error) { +func (s *idpServer) CreateIdp(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, error) { return s.IdpService.CreateIdp(ctx, idp) } -func (s *idpServer) UpdateIdp(ctx context.Context, idp *userv3.UpdateIdp) (*userv3.Idp, error) { +func (s *idpServer) GetIdp(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, error) { + return s.IdpService.GetIdp(ctx, idp) +} + +func (s *idpServer) ListIdps(ctx context.Context, _ *emptypb.Empty) (*userv3.IdpList, error) { + return s.IdpService.ListIdps(ctx) +} + +func (s *idpServer) UpdateIdp(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, error) { return s.IdpService.UpdateIdp(ctx, idp) } -func (s *idpServer) GetSpConfigById(ctx context.Context, idpID *userv3.IdpID) (*userv3.SpConfig, error) { - return s.IdpService.GetSpConfigById(ctx, idpID) -} - -func (s *idpServer) ListIdps(ctx context.Context, req *userv3.ListIdpsRequest) (*userv3.ListIdpsResponse, error) { - return s.IdpService.ListIdps(ctx, req) -} - -func (s *idpServer) DeleteIdp(ctx context.Context, idpID *userv3.IdpID) (*emptypb.Empty, error) { - return s.IdpService.DeleteIdp(ctx, idpID) +func (s *idpServer) DeleteIdp(ctx context.Context, idpID *userv3.Idp) (*emptypb.Empty, error) { + return &emptypb.Empty{}, s.IdpService.DeleteIdp(ctx, idpID) } diff --git a/components/usermgmt/pkg/service/idp.go b/components/usermgmt/pkg/service/idp.go index c98d7f6..0a06abd 100644 --- a/components/usermgmt/pkg/service/idp.go +++ b/components/usermgmt/pkg/service/idp.go @@ -16,21 +16,19 @@ import ( "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" "github.com/uptrace/bun" - "google.golang.org/protobuf/types/known/emptypb" ) -const TimeLayout = "2006-01-02T15:04:05.999999Z" - type IdpService interface { - CreateIdp(context.Context, *userv3.NewIdp) (*userv3.Idp, error) - UpdateIdp(context.Context, *userv3.UpdateIdp) (*userv3.Idp, error) - GetSpConfigById(context.Context, *userv3.IdpID) (*userv3.SpConfig, error) - ListIdps(context.Context, *userv3.ListIdpsRequest) (*userv3.ListIdpsResponse, error) - DeleteIdp(context.Context, *userv3.IdpID) (*emptypb.Empty, error) + CreateIdp(context.Context, *userv3.Idp) (*userv3.Idp, error) + GetIdp(context.Context, *userv3.Idp) (*userv3.Idp, error) + ListIdps(context.Context) (*userv3.IdpList, error) + UpdateIdp(context.Context, *userv3.Idp) (*userv3.Idp, error) + DeleteIdp(context.Context, *userv3.Idp) error } type idpService struct { @@ -103,9 +101,9 @@ func generateSpCert(host string) (string, string, error) { return string(cPEMBytes), string(privPEMBytes), nil } -func (s *idpService) CreateIdp(ctx context.Context, idp *userv3.NewIdp) (*userv3.Idp, error) { - name := idp.GetName() - domain := idp.GetDomain() +func (s *idpService) CreateIdp(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, error) { + name := idp.Metadata.GetName() + domain := idp.Spec.GetDomain() e := &models.Idp{} s.dao.GetByName(ctx, name, e) @@ -124,13 +122,17 @@ func (s *idpService) CreateIdp(ctx context.Context, idp *userv3.NewIdp) (*userv3 acsURL := generateAcsURL(base.String()) entity := &models.Idp{ Name: name, - IdpName: idp.GetIdpName(), + Description: idp.Metadata.GetDescription(), + CreatedAt: time.Now(), + IdpName: idp.Spec.GetIdpName(), Domain: domain, AcsURL: acsURL, - GroupAttributeName: idp.GetGroupAttributeName(), - SaeEnabled: idp.GetIsSaeEnabled(), - CreatedAt: time.Now(), - ModifiedAt: time.Now(), + SsoURL: idp.Spec.GetSsoUrl(), + IdpCert: idp.Spec.GetIdpCert(), + MetadataURL: idp.Spec.GetMetadataUrl(), + MetadataFilename: idp.Spec.GetMetadataFilename(), + GroupAttributeName: idp.Spec.GetGroupAttributeName(), + SaeEnabled: idp.Spec.GetSaeEnabled(), } if entity.SaeEnabled { spcert, spkey, err := generateSpCert(base.Host) @@ -146,41 +148,92 @@ func (s *idpService) CreateIdp(ctx context.Context, idp *userv3.NewIdp) (*userv3 } rv := &userv3.Idp{ - Id: entity.Id.String(), - Name: entity.Name, - IdpName: entity.IdpName, - Domain: entity.Domain, - AcsUrl: entity.AcsURL, - SsoUrl: entity.SsoURL, - IdpCert: entity.IdpCert, - SpCert: entity.SpCert, - MetadataUrl: entity.MetadataURL, - MetadataFilename: entity.MetadataFilename, - IsSaeEnabled: entity.SaeEnabled, - GroupAttributeName: entity.GroupAttributeName, - OrganizationId: entity.OrganizationId.String(), - PartnerId: entity.PartnerId.String(), - CreatedAt: entity.CreatedAt.Format(TimeLayout), - ModifiedAt: entity.ModifiedAt.Format(TimeLayout), + ApiVersion: "usermgmt.k8smgmt.io/v3", + Kind: "Idp", + Metadata: &commonv3.Metadata{ + Name: entity.Name, + Organization: entity.OrganizationId.String(), + Partner: entity.PartnerId.String(), + Id: entity.Id.String(), + }, + Spec: &userv3.IdpSpec{ + IdpName: entity.IdpName, + Domain: entity.Domain, + AcsUrl: entity.AcsURL, + SsoUrl: entity.SsoURL, + IdpCert: entity.IdpCert, + SpCert: entity.SpCert, + MetadataUrl: entity.MetadataURL, + MetadataFilename: entity.MetadataFilename, + SaeEnabled: entity.SaeEnabled, + GroupAttributeName: entity.GroupAttributeName, + NameIdFormat: "Email Address", + ConsumerBinding: "HTTP-POST", + SpEntityId: entity.AcsURL, + }, } return rv, nil } -func (s *idpService) UpdateIdp(ctx context.Context, new *userv3.UpdateIdp) (*userv3.Idp, error) { - id, err := uuid.Parse(new.GetId()) +func (s *idpService) GetIdp(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, error) { + id, err := uuid.Parse(idp.Metadata.GetId()) + if err != nil { + return &userv3.Idp{}, err + } + entity := &models.Idp{} + _, err = s.dao.GetByID(ctx, id, entity) + if err != nil { + return &userv3.Idp{}, err + } + if entity.Id != id { + return &userv3.Idp{}, fmt.Errorf("IDP ID DOES NOT EXISTS") + } + rv := &userv3.Idp{ + ApiVersion: "usermgmt.k8smgmt.io/v3", + Kind: "Idp", + Metadata: &commonv3.Metadata{ + Name: entity.Name, + Organization: entity.OrganizationId.String(), + Partner: entity.PartnerId.String(), + Id: entity.Id.String(), + }, + Spec: &userv3.IdpSpec{ + IdpName: entity.IdpName, + Domain: entity.Domain, + AcsUrl: entity.AcsURL, + SsoUrl: entity.SsoURL, + IdpCert: entity.IdpCert, + SpCert: entity.SpCert, + MetadataUrl: entity.MetadataURL, + MetadataFilename: entity.MetadataFilename, + SaeEnabled: entity.SaeEnabled, + GroupAttributeName: entity.GroupAttributeName, + NameIdFormat: "Email Address", + ConsumerBinding: "HTTP-POST", + SpEntityId: entity.AcsURL, + }, + } + return rv, nil +} + +func (s *idpService) UpdateIdp(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, error) { + id, err := uuid.Parse(idp.Metadata.GetId()) if err != nil { return &userv3.Idp{}, err } entity := &models.Idp{ - Id: id, - Name: new.GetName(), + Name: idp.Metadata.GetName(), + Description: idp.Metadata.GetDescription(), ModifiedAt: time.Now(), - IdpName: new.GetIdpName(), - Domain: new.GetDomain(), - AcsURL: new.GetAcsUrl(), - MetadataURL: new.GetMetadataUrl(), - GroupAttributeName: new.GetGroupAttributeName(), - SaeEnabled: new.GetIsSaeEnabled(), + IdpName: idp.Spec.GetIdpName(), + Domain: idp.Spec.GetDomain(), + AcsURL: idp.Spec.GetAcsUrl(), + SsoURL: idp.Spec.GetSsoUrl(), + IdpCert: idp.Spec.GetIdpCert(), + MetadataURL: idp.Spec.GetMetadataUrl(), + MetadataFilename: idp.Spec.GetMetadataFilename(), + GroupAttributeName: idp.Spec.GetGroupAttributeName(), + SaeEnabled: idp.Spec.GetSaeEnabled(), } if entity.SaeEnabled { base, err := url.Parse(os.Getenv("APP_HOST_HTTP")) @@ -199,52 +252,34 @@ func (s *idpService) UpdateIdp(ctx context.Context, new *userv3.UpdateIdp) (*use return &userv3.Idp{}, err } rv := &userv3.Idp{ - Id: entity.Id.String(), - Name: entity.Name, - IdpName: entity.IdpName, - Domain: entity.Domain, - AcsUrl: entity.AcsURL, - SsoUrl: entity.SsoURL, - IdpCert: entity.IdpCert, - SpCert: entity.SpCert, - MetadataUrl: entity.MetadataURL, - MetadataFilename: entity.MetadataFilename, - IsSaeEnabled: entity.SaeEnabled, - GroupAttributeName: entity.GroupAttributeName, - OrganizationId: entity.OrganizationId.String(), - PartnerId: entity.PartnerId.String(), - CreatedAt: entity.CreatedAt.Format(TimeLayout), - ModifiedAt: entity.ModifiedAt.Format(TimeLayout), + ApiVersion: "usermgmt.k8smgmt.io/v3", + Kind: "Idp", + Metadata: &commonv3.Metadata{ + Name: entity.Name, + Organization: entity.OrganizationId.String(), + Partner: entity.PartnerId.String(), + Id: entity.Id.String(), + }, + Spec: &userv3.IdpSpec{ + IdpName: entity.IdpName, + Domain: entity.Domain, + AcsUrl: entity.AcsURL, + SsoUrl: entity.SsoURL, + IdpCert: entity.IdpCert, + SpCert: entity.SpCert, + MetadataUrl: entity.MetadataURL, + MetadataFilename: entity.MetadataFilename, + SaeEnabled: entity.SaeEnabled, + GroupAttributeName: entity.GroupAttributeName, + NameIdFormat: "Email Address", + ConsumerBinding: "HTTP-POST", + SpEntityId: entity.AcsURL, + }, } return rv, nil } -func (s *idpService) GetSpConfigById(ctx context.Context, idpID *userv3.IdpID) (*userv3.SpConfig, error) { - id, err := uuid.Parse(idpID.GetId()) - if err != nil { - return &userv3.SpConfig{}, err - } - - entity := &models.Idp{} - _, err = s.dao.GetByID(ctx, id, entity) - if err != nil { - return &userv3.SpConfig{}, err - } - if entity.Id != id { - return &userv3.SpConfig{}, fmt.Errorf("IDP ID DOES NOT EXISTS") - } - rv := &userv3.SpConfig{ - NameidFormat: "Email Address", - ConsumerBinding: "HTTP-POST", - AcsUrl: entity.AcsURL, - EntityId: entity.AcsURL, - GroupAttributeName: entity.GroupAttributeName, - SpCert: entity.SpCert, - } - return rv, nil -} - -func (s *idpService) ListIdps(ctx context.Context, req *userv3.ListIdpsRequest) (*userv3.ListIdpsResponse, error) { +func (s *idpService) ListIdps(ctx context.Context) (*userv3.IdpList, error) { var entities []models.Idp var orgID uuid.NullUUID var parID uuid.NullUUID @@ -254,45 +289,51 @@ func (s *idpService) ListIdps(ctx context.Context, req *userv3.ListIdpsRequest) var result []*userv3.Idp for _, entity := range entities { e := &userv3.Idp{ - Id: entity.Id.String(), - Name: entity.Name, - IdpName: entity.IdpName, - Domain: entity.Domain, - AcsUrl: entity.AcsURL, - SsoUrl: entity.SsoURL, - IdpCert: entity.IdpCert, - SpCert: entity.SpCert, - MetadataUrl: entity.MetadataURL, - MetadataFilename: entity.MetadataFilename, - IsSaeEnabled: entity.SaeEnabled, - GroupAttributeName: entity.GroupAttributeName, - OrganizationId: entity.OrganizationId.String(), - PartnerId: entity.PartnerId.String(), - CreatedAt: entity.CreatedAt.Format(TimeLayout), - ModifiedAt: entity.ModifiedAt.Format(TimeLayout), + ApiVersion: "usermgmt.k8smgmt.io/v3", + Kind: "Idp", + Metadata: &commonv3.Metadata{ + Name: entity.Name, + Organization: entity.OrganizationId.String(), + Partner: entity.PartnerId.String(), + Id: entity.Id.String(), + }, + Spec: &userv3.IdpSpec{ + IdpName: entity.IdpName, + Domain: entity.Domain, + AcsUrl: entity.AcsURL, + SsoUrl: entity.SsoURL, + IdpCert: entity.IdpCert, + SpCert: entity.SpCert, + MetadataUrl: entity.MetadataURL, + MetadataFilename: entity.MetadataFilename, + SaeEnabled: entity.SaeEnabled, + GroupAttributeName: entity.GroupAttributeName, + NameIdFormat: "Email Address", + ConsumerBinding: "HTTP-POST", + SpEntityId: entity.AcsURL, + }, } result = append(result, e) } - rv := &userv3.ListIdpsResponse{ - Count: int32(len(entities)), - Next: 0, - Previous: 0, - Result: result, + rv := &userv3.IdpList{ + ApiVersion: "usermgmt.k8smgmt.io/v3", + Kind: "Idp", + Items: result, } return rv, nil } -func (s *idpService) DeleteIdp(ctx context.Context, idpID *userv3.IdpID) (*emptypb.Empty, error) { - id, err := uuid.Parse(idpID.GetId()) +func (s *idpService) DeleteIdp(ctx context.Context, idp *userv3.Idp) error { + id, err := uuid.Parse(idp.Metadata.GetId()) if err != nil { - return &emptypb.Empty{}, err + return err } entity := &models.Idp{} err = s.dao.Delete(ctx, id, entity) if err != nil { - return &emptypb.Empty{}, err + return err } - return &emptypb.Empty{}, nil + return nil } From 9f8ebe2fd8b61189a5f2bdc4b2d6d3d9ca20c390 Mon Sep 17 00:00:00 2001 From: Akshay Gaikwad Date: Fri, 21 Jan 2022 12:31:58 +0530 Subject: [PATCH 7/8] Change name of Idp Service methods Being consistent with other resource names. --- components/usermgmt/pkg/server/idp.go | 10 +++++----- components/usermgmt/pkg/service/idp.go | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/components/usermgmt/pkg/server/idp.go b/components/usermgmt/pkg/server/idp.go index 83a4124..56116e6 100644 --- a/components/usermgmt/pkg/server/idp.go +++ b/components/usermgmt/pkg/server/idp.go @@ -18,21 +18,21 @@ func NewIdpServer(is service.IdpService) rpcv3.IdpServer { } func (s *idpServer) CreateIdp(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, error) { - return s.IdpService.CreateIdp(ctx, idp) + return s.IdpService.Create(ctx, idp) } func (s *idpServer) GetIdp(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, error) { - return s.IdpService.GetIdp(ctx, idp) + return s.IdpService.GetByID(ctx, idp) } func (s *idpServer) ListIdps(ctx context.Context, _ *emptypb.Empty) (*userv3.IdpList, error) { - return s.IdpService.ListIdps(ctx) + return s.IdpService.List(ctx) } func (s *idpServer) UpdateIdp(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, error) { - return s.IdpService.UpdateIdp(ctx, idp) + return s.IdpService.Update(ctx, idp) } func (s *idpServer) DeleteIdp(ctx context.Context, idpID *userv3.Idp) (*emptypb.Empty, error) { - return &emptypb.Empty{}, s.IdpService.DeleteIdp(ctx, idpID) + return &emptypb.Empty{}, s.IdpService.Delete(ctx, idpID) } diff --git a/components/usermgmt/pkg/service/idp.go b/components/usermgmt/pkg/service/idp.go index 0a06abd..06ff6f5 100644 --- a/components/usermgmt/pkg/service/idp.go +++ b/components/usermgmt/pkg/service/idp.go @@ -24,11 +24,11 @@ import ( ) type IdpService interface { - CreateIdp(context.Context, *userv3.Idp) (*userv3.Idp, error) - GetIdp(context.Context, *userv3.Idp) (*userv3.Idp, error) - ListIdps(context.Context) (*userv3.IdpList, error) - UpdateIdp(context.Context, *userv3.Idp) (*userv3.Idp, error) - DeleteIdp(context.Context, *userv3.Idp) error + Create(context.Context, *userv3.Idp) (*userv3.Idp, error) + GetByID(context.Context, *userv3.Idp) (*userv3.Idp, error) + List(context.Context) (*userv3.IdpList, error) + Update(context.Context, *userv3.Idp) (*userv3.Idp, error) + Delete(context.Context, *userv3.Idp) error } type idpService struct { @@ -101,7 +101,7 @@ func generateSpCert(host string) (string, string, error) { return string(cPEMBytes), string(privPEMBytes), nil } -func (s *idpService) CreateIdp(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, error) { +func (s *idpService) Create(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, error) { name := idp.Metadata.GetName() domain := idp.Spec.GetDomain() @@ -175,7 +175,7 @@ func (s *idpService) CreateIdp(ctx context.Context, idp *userv3.Idp) (*userv3.Id return rv, nil } -func (s *idpService) GetIdp(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, error) { +func (s *idpService) GetByID(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, error) { id, err := uuid.Parse(idp.Metadata.GetId()) if err != nil { return &userv3.Idp{}, err @@ -216,7 +216,7 @@ func (s *idpService) GetIdp(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, return rv, nil } -func (s *idpService) UpdateIdp(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, error) { +func (s *idpService) Update(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, error) { id, err := uuid.Parse(idp.Metadata.GetId()) if err != nil { return &userv3.Idp{}, err @@ -279,7 +279,7 @@ func (s *idpService) UpdateIdp(ctx context.Context, idp *userv3.Idp) (*userv3.Id return rv, nil } -func (s *idpService) ListIdps(ctx context.Context) (*userv3.IdpList, error) { +func (s *idpService) List(ctx context.Context) (*userv3.IdpList, error) { var entities []models.Idp var orgID uuid.NullUUID var parID uuid.NullUUID @@ -324,7 +324,7 @@ func (s *idpService) ListIdps(ctx context.Context) (*userv3.IdpList, error) { return rv, nil } -func (s *idpService) DeleteIdp(ctx context.Context, idp *userv3.Idp) error { +func (s *idpService) Delete(ctx context.Context, idp *userv3.Idp) error { id, err := uuid.Parse(idp.Metadata.GetId()) if err != nil { return err From fc4f5802a5dec89da9a773b4968695197b20636c Mon Sep 17 00:00:00 2001 From: Akshay Gaikwad Date: Fri, 21 Jan 2022 16:05:00 +0530 Subject: [PATCH 8/8] Some minor modifications in the Idp service --- components/usermgmt/pkg/service/idp.go | 88 ++++++++++++++++++++------ 1 file changed, 67 insertions(+), 21 deletions(-) diff --git a/components/usermgmt/pkg/service/idp.go b/components/usermgmt/pkg/service/idp.go index 06ff6f5..f5fc417 100644 --- a/components/usermgmt/pkg/service/idp.go +++ b/components/usermgmt/pkg/service/idp.go @@ -23,6 +23,20 @@ import ( "github.com/uptrace/bun" ) +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 IdpService interface { Create(context.Context, *userv3.Idp) (*userv3.Idp, error) GetByID(context.Context, *userv3.Idp) (*userv3.Idp, error) @@ -41,10 +55,9 @@ func NewIdpService(db *bun.DB) IdpService { } } -func generateAcsURL(baseURL string) string { +func generateAcsURL() (string, error) { uuid := uuid.New() - acsURL := fmt.Sprintf("%s/%s/", baseURL, uuid.String()) - return acsURL + return fmt.Sprintf("%s/%s/", baseUrl.String(), uuid.String()), nil } // generateSpCert generates self signed certificate. Returns cert and @@ -105,6 +118,13 @@ func (s *idpService) Create(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, name := idp.Metadata.GetName() domain := idp.Spec.GetDomain() + // validate name and domain + if len(name) == 0 { + return &userv3.Idp{}, fmt.Errorf("EMPTY NAME") + } + if len(domain) == 0 { + return &userv3.Idp{}, fmt.Errorf("EMPTY DOMAIN") + } e := &models.Idp{} s.dao.GetByName(ctx, name, e) if e.Name == name { @@ -115,11 +135,10 @@ func (s *idpService) Create(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, return &userv3.Idp{}, fmt.Errorf("DUPLICATE DOMAIN") } - base, err := url.Parse(os.Getenv("APP_HOST_HTTP")) + acsURL, err := generateAcsURL() if err != nil { return &userv3.Idp{}, err } - acsURL := generateAcsURL(base.String()) entity := &models.Idp{ Name: name, Description: idp.Metadata.GetDescription(), @@ -135,7 +154,7 @@ func (s *idpService) Create(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, SaeEnabled: idp.Spec.GetSaeEnabled(), } if entity.SaeEnabled { - spcert, spkey, err := generateSpCert(base.Host) + spcert, spkey, err := generateSpCert(baseUrl.Host) if err != nil { return &userv3.Idp{}, err } @@ -181,13 +200,11 @@ func (s *idpService) GetByID(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, return &userv3.Idp{}, err } entity := &models.Idp{} + // TODO: Check for existance of id before GetByID _, err = s.dao.GetByID(ctx, id, entity) if err != nil { return &userv3.Idp{}, err } - if entity.Id != id { - return &userv3.Idp{}, fmt.Errorf("IDP ID DOES NOT EXISTS") - } rv := &userv3.Idp{ ApiVersion: "usermgmt.k8smgmt.io/v3", Kind: "Idp", @@ -217,17 +234,40 @@ func (s *idpService) GetByID(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, } func (s *idpService) Update(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, error) { + var id, orgId, partId uuid.UUID id, err := uuid.Parse(idp.Metadata.GetId()) + // TODO: 400 Bad Request if err != nil { return &userv3.Idp{}, err } + if len(idp.Metadata.GetOrganization()) != 0 { + orgId, err = uuid.Parse(idp.Metadata.GetOrganization()) + if err != nil { + return &userv3.Idp{}, err + } + } + if len(idp.Metadata.GetPartner()) != 0 { + partId, err = uuid.Parse(idp.Metadata.GetPartner()) + if err != nil { + return &userv3.Idp{}, err + } + } + _, err = s.dao.GetByID(ctx, id, &models.Idp{}) + // TODO: Return proper error for Id not exist + if err != nil { + return &userv3.Idp{}, err + } + entity := &models.Idp{ + Id: id, Name: idp.Metadata.GetName(), Description: idp.Metadata.GetDescription(), ModifiedAt: time.Now(), IdpName: idp.Spec.GetIdpName(), Domain: idp.Spec.GetDomain(), AcsURL: idp.Spec.GetAcsUrl(), + OrganizationId: orgId, + PartnerId: partId, SsoURL: idp.Spec.GetSsoUrl(), IdpCert: idp.Spec.GetIdpCert(), MetadataURL: idp.Spec.GetMetadataUrl(), @@ -236,17 +276,14 @@ func (s *idpService) Update(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, SaeEnabled: idp.Spec.GetSaeEnabled(), } if entity.SaeEnabled { - base, err := url.Parse(os.Getenv("APP_HOST_HTTP")) - if err != nil { - return &userv3.Idp{}, err - } - spcert, spkey, err := generateSpCert(base.Host) + spcert, spkey, err := generateSpCert(baseUrl.Host) if err != nil { return &userv3.Idp{}, err } entity.SpCert = spcert entity.SpKey = spkey } + _, err = s.dao.Update(ctx, id, entity) if err != nil { return &userv3.Idp{}, err @@ -280,10 +317,15 @@ func (s *idpService) Update(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, } func (s *idpService) List(ctx context.Context) (*userv3.IdpList, error) { - var entities []models.Idp - var orgID uuid.NullUUID - var parID uuid.NullUUID - s.dao.List(ctx, parID, orgID, &entities) + var ( + entities []models.Idp + orgID uuid.NullUUID + parID uuid.NullUUID + ) + _, err := s.dao.List(ctx, parID, orgID, &entities) + if err != nil { + return &userv3.IdpList{}, err + } // Get idps only till limit var result []*userv3.Idp @@ -318,7 +360,7 @@ func (s *idpService) List(ctx context.Context) (*userv3.IdpList, error) { rv := &userv3.IdpList{ ApiVersion: "usermgmt.k8smgmt.io/v3", - Kind: "Idp", + Kind: "IdpList", Items: result, } return rv, nil @@ -329,9 +371,13 @@ func (s *idpService) Delete(ctx context.Context, idp *userv3.Idp) error { if err != nil { return err } - entity := &models.Idp{} - err = s.dao.Delete(ctx, id, entity) + _, 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.Idp{}) if err != nil { return err }