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; +}