diff --git a/changelog/unreleased/runtime-plugins.md b/changelog/unreleased/runtime-plugins.md new file mode 100644 index 0000000000..1f21a12830 --- /dev/null +++ b/changelog/unreleased/runtime-plugins.md @@ -0,0 +1,5 @@ +Enhancement: Add support for runtime plugins + +This PR introduces a new plugin package, that allows loading external plugins into Reva at runtime. The hashicorp go-plugin framework was used to facilitate the plugin loading and communication. + +https://github.com/cs3org/reva/pull/1861 \ No newline at end of file diff --git a/examples/plugin/json/json.go b/examples/plugin/json/json.go new file mode 100644 index 0000000000..ce9cd59c54 --- /dev/null +++ b/examples/plugin/json/json.go @@ -0,0 +1,162 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package main + +import ( + "context" + "encoding/json" + "errors" + "io/ioutil" + "strings" + + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/user" + "github.com/hashicorp/go-plugin" + "github.com/mitchellh/mapstructure" +) + +// Manager is a real implementation of Manager interface. +type Manager struct { + users []*userpb.User +} + +type config struct { + Users string `mapstructure:"users"` +} + +func (c *config) init() { + if c.Users == "" { + c.Users = "/etc/revad/users.json" + } +} + +func parseConfig(m map[string]interface{}) (*config, error) { + c := &config{} + if err := mapstructure.Decode(m, c); err != nil { + return nil, err + } + c.init() + return c, nil +} + +// Configure initializes the manager struct based on the configurations. +func (m *Manager) Configure(ml map[string]interface{}) error { + c, err := parseConfig(ml) + if err != nil { + return err + } + + f, err := ioutil.ReadFile(c.Users) + if err != nil { + return err + } + + users := []*userpb.User{} + + err = json.Unmarshal(f, &users) + if err != nil { + return err + } + + m.users = users + + return nil +} + +// GetUser returns the user based on the uid. +func (m *Manager) GetUser(ctx context.Context, uid *userpb.UserId) (*userpb.User, error) { + for _, u := range m.users { + if (u.Id.GetOpaqueId() == uid.OpaqueId || u.Username == uid.OpaqueId) && (uid.Idp == "" || uid.Idp == u.Id.GetIdp()) { + return u, nil + } + } + return nil, nil +} + +// GetUserByClaim returns user based on the claim +func (m *Manager) GetUserByClaim(ctx context.Context, claim, value string) (*userpb.User, error) { + for _, u := range m.users { + if userClaim, err := extractClaim(u, claim); err == nil && value == userClaim { + return u, nil + } + } + return nil, errtypes.NotFound(value) +} + +func extractClaim(u *userpb.User, claim string) (string, error) { + switch claim { + case "mail": + return u.Mail, nil + case "username": + return u.Username, nil + case "uid": + if u.Opaque != nil && u.Opaque.Map != nil { + if uidObj, ok := u.Opaque.Map["uid"]; ok { + if uidObj.Decoder == "plain" { + return string(uidObj.Value), nil + } + } + } + } + return "", errors.New("json: invalid field") +} + +// TODO(jfd) search Opaque? compare sub? +func userContains(u *userpb.User, query string) bool { + query = strings.ToLower(query) + return strings.Contains(strings.ToLower(u.Username), query) || strings.Contains(strings.ToLower(u.DisplayName), query) || + strings.Contains(strings.ToLower(u.Mail), query) || strings.Contains(strings.ToLower(u.Id.OpaqueId), query) +} + +// FindUsers returns the user based on the query +func (m *Manager) FindUsers(ctx context.Context, query string) ([]*userpb.User, error) { + users := []*userpb.User{} + for _, u := range m.users { + if userContains(u, query) { + users = append(users, u) + } + } + return users, nil +} + +// GetUserGroups returns the user groups +func (m *Manager) GetUserGroups(ctx context.Context, uid *userpb.UserId) ([]string, error) { + user, err := m.GetUser(ctx, uid) + if err != nil { + return nil, err + } + return user.Groups, nil +} + +// Handshake hashicorp go-plugin handshake +var Handshake = plugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "BASIC_PLUGIN", + MagicCookieValue: "hello", +} + +func main() { + plugin.Serve(&plugin.ServeConfig{ + HandshakeConfig: Handshake, + Plugins: map[string]plugin.Plugin{ + "userprovider": &user.ProviderPlugin{Impl: &Manager{}}, + }, + }) +} diff --git a/examples/plugin/plugin.toml b/examples/plugin/plugin.toml new file mode 100644 index 0000000000..28bc78198c --- /dev/null +++ b/examples/plugin/plugin.toml @@ -0,0 +1,15 @@ +[shared] +gatewaysvc = "localhost:19000" +plugin = true + +[grpc] +address = "0.0.0.0:19000" + +[grpc.services.gateway] +userprovidersvc = "localhost:19000" + +[grpc.services.userprovider] +driver = "./json" + +[grpc.services.userprovider.drivers.json] +users = "users.demo.json" diff --git a/examples/plugin/users.demo.json b/examples/plugin/users.demo.json new file mode 100644 index 0000000000..21479c1ec7 --- /dev/null +++ b/examples/plugin/users.demo.json @@ -0,0 +1,102 @@ +[ + { + "id": { + "opaque_id": "4c510ada-c86b-4815-8820-42cdf82c3d51", + "idp": "cernbox.cern.ch" + }, + "username": "einstein", + "secret": "relativity", + "mail": "einstein@cern.ch", + "display_name": "Albert Einstein", + "groups": ["sailing-lovers", "violin-haters", "physics-lovers"], + "opaque": { + "map": { + "gid": { + "_comment": "decodes to 987", + "decoder":"plain", + "value":"OTg3" + }, + "uid":{ + "_comment": "decodes to 123", + "decoder":"plain", + "value":"MTIz" + } + } + } + }, + { + "id": { + "opaque_id": "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", + "idp": "cesnet.cz" + }, + "username": "marie", + "secret": "radioactivity", + "mail": "marie@cesnet.cz", + "display_name": "Marie Curie", + "groups": ["radium-lovers", "polonium-lovers", "physics-lovers"], + "opaque": { + "map": { + "gid": { + "_comment": "decodes to 987", + "decoder":"plain", + "value":"OTg3" + }, + "uid":{ + "_comment": "decodes to 456", + "decoder":"plain", + "value":"NDU2" + } + } + } + }, + { + "id": { + "opaque_id": "932b4540-8d16-481e-8ef4-588e4b6b151c", + "idp": "example.org" + }, + "username": "richard", + "secret": "superfluidity", + "mail": "richard@example.org", + "display_name": "Richard Feynman", + "groups": ["quantum-lovers", "philosophy-haters", "physics-lovers"], + "opaque": { + "map": { + "gid": { + "_comment": "decodes to 135", + "decoder":"plain", + "value":"MTM1" + }, + "uid":{ + "_comment": "decodes to 246", + "decoder":"plain", + "value":"MjQ2" + } + } + } + }, + { + "id": { + "opaque_id": "932b4522-139b-4815-8ef4-42cdf82c3d51", + "idp": "example.com" + }, + "username": "test", + "secret": "test", + "mail": "test@example.com", + "display_name": "Test Testman", + "groups": ["quantum-lovers", "philosophy-haters", "physics-lovers"], + "opaque": { + "map": { + "gid": { + "_comment": "decodes to 135", + "decoder":"plain", + "value":"MTM1" + }, + "uid":{ + "_comment": "decodes to 468", + "decoder":"plain", + "value":"NDY4" + } + } + } + } +] diff --git a/go.mod b/go.mod index f96ad10488..841dccae87 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,8 @@ require ( github.com/google/go-querystring v1.0.0 // indirect github.com/google/uuid v1.3.0 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 + github.com/hashicorp/go-hclog v0.14.1 + github.com/hashicorp/go-plugin v1.4.2 github.com/huandu/xstrings v1.3.0 // indirect github.com/imdario/mergo v0.3.8 // indirect github.com/jedib0t/go-pretty v4.3.0+incompatible diff --git a/go.sum b/go.sum index 91a4007cd3..bf5cc74996 100644 --- a/go.sum +++ b/go.sum @@ -212,6 +212,7 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -273,9 +274,16 @@ github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoP github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-plugin v1.4.2 h1:yFvG3ufXXpqiMiZx9HLcaK3XbIqQ1WJFR/F1a2CuVw0= +github.com/hashicorp/go-plugin v1.4.2/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= @@ -291,6 +299,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.0 h1:gvV6jG9dTgFEncxo+AF7PH6MZXi/vZl25owA/8Dg8Wo= github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= @@ -302,6 +312,8 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo= github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag= +github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= +github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -383,6 +395,8 @@ github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFW github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= @@ -418,6 +432,7 @@ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -638,6 +653,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -828,6 +844,7 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= diff --git a/internal/grpc/services/authprovider/authprovider.go b/internal/grpc/services/authprovider/authprovider.go index 535e388a93..f3a27dbd82 100644 --- a/internal/grpc/services/authprovider/authprovider.go +++ b/internal/grpc/services/authprovider/authprovider.go @@ -21,12 +21,14 @@ package authprovider import ( "context" "fmt" + "path/filepath" provider "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/auth" "github.com/cs3org/reva/pkg/auth/manager/registry" "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/plugin" "github.com/cs3org/reva/pkg/rgrpc" "github.com/cs3org/reva/pkg/rgrpc/status" "github.com/mitchellh/mapstructure" @@ -52,6 +54,7 @@ func (c *config) init() { type service struct { authmgr auth.Manager conf *config + plugin *plugin.RevaPlugin } func parseConfig(m map[string]interface{}) (*config, error) { @@ -64,14 +67,31 @@ func parseConfig(m map[string]interface{}) (*config, error) { return c, nil } -func getAuthManager(manager string, m map[string]map[string]interface{}) (auth.Manager, error) { +func getAuthManager(manager string, m map[string]map[string]interface{}) (auth.Manager, *plugin.RevaPlugin, error) { if manager == "" { - return nil, errtypes.InternalError("authsvc: driver not configured for auth manager") + return nil, nil, errtypes.InternalError("authsvc: driver not configured for auth manager") } - if f, ok := registry.NewFuncs[manager]; ok { - return f(m[manager]) + p, err := plugin.Load("authprovider", manager) + if err == nil { + authManager, ok := p.Plugin.(auth.Manager) + if !ok { + return nil, nil, fmt.Errorf("could not assert the loaded plugin") + } + pluginConfig := filepath.Base(manager) + err = authManager.Configure(m[pluginConfig]) + if err != nil { + return nil, nil, err + } + return authManager, p, nil + } else if _, ok := err.(errtypes.NotFound); ok { + if f, ok := registry.NewFuncs[manager]; ok { + authmgr, err := f(m[manager]) + return authmgr, nil, err + } + } else { + return nil, nil, err } - return nil, errtypes.NotFound(fmt.Sprintf("authsvc: driver %s not found for auth manager", manager)) + return nil, nil, errtypes.NotFound(fmt.Sprintf("authsvc: driver %s not found for auth manager", manager)) } // New returns a new AuthProviderServiceServer. @@ -81,17 +101,24 @@ func New(m map[string]interface{}, ss *grpc.Server) (rgrpc.Service, error) { return nil, err } - authManager, err := getAuthManager(c.AuthManager, c.AuthManagers) + authManager, plug, err := getAuthManager(c.AuthManager, c.AuthManagers) if err != nil { return nil, err } - svc := &service{conf: c, authmgr: authManager} + svc := &service{ + conf: c, + authmgr: authManager, + plugin: plug, + } return svc, nil } func (s *service) Close() error { + if s.plugin != nil { + s.plugin.Kill() + } return nil } diff --git a/internal/grpc/services/userprovider/userprovider.go b/internal/grpc/services/userprovider/userprovider.go index 376f00b694..15462e38f1 100644 --- a/internal/grpc/services/userprovider/userprovider.go +++ b/internal/grpc/services/userprovider/userprovider.go @@ -21,10 +21,12 @@ package userprovider import ( "context" "fmt" + "path/filepath" "sort" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/plugin" "github.com/cs3org/reva/pkg/rgrpc" "github.com/cs3org/reva/pkg/rgrpc/status" "github.com/cs3org/reva/pkg/user" @@ -59,12 +61,29 @@ func parseConfig(m map[string]interface{}) (*config, error) { return c, nil } -func getDriver(c *config) (user.Manager, error) { - if f, ok := registry.NewFuncs[c.Driver]; ok { - return f(c.Drivers[c.Driver]) +func getDriver(c *config) (user.Manager, *plugin.RevaPlugin, error) { + p, err := plugin.Load("userprovider", c.Driver) + if err == nil { + manager, ok := p.Plugin.(user.Manager) + if !ok { + return nil, nil, fmt.Errorf("could not assert the loaded plugin") + } + pluginConfig := filepath.Base(c.Driver) + err = manager.Configure(c.Drivers[pluginConfig]) + if err != nil { + return nil, nil, err + } + return manager, p, nil + } else if _, ok := err.(errtypes.NotFound); ok { + // plugin not found, fetch the driver from the in-memory registry + if f, ok := registry.NewFuncs[c.Driver]; ok { + mgr, err := f(c.Drivers[c.Driver]) + return mgr, nil, err + } + } else { + return nil, nil, err } - - return nil, errtypes.NotFound(fmt.Sprintf("driver %s not found for user manager", c.Driver)) + return nil, nil, errtypes.NotFound(fmt.Sprintf("driver %s not found for user manager", c.Driver)) } // New returns a new UserProviderServiceServer. @@ -73,22 +92,27 @@ func New(m map[string]interface{}, ss *grpc.Server) (rgrpc.Service, error) { if err != nil { return nil, err } - - userManager, err := getDriver(c) + userManager, plug, err := getDriver(c) if err != nil { return nil, err } - - svc := &service{usermgr: userManager} + svc := &service{ + usermgr: userManager, + plugin: plug, + } return svc, nil } type service struct { usermgr user.Manager + plugin *plugin.RevaPlugin } func (s *service) Close() error { + if s.plugin != nil { + s.plugin.Kill() + } return nil } diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go index 8714099683..a3cd390338 100644 --- a/pkg/auth/auth.go +++ b/pkg/auth/auth.go @@ -25,10 +25,12 @@ import ( authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" registry "github.com/cs3org/go-cs3apis/cs3/auth/registry/v1beta1" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + "github.com/cs3org/reva/pkg/plugin" ) // Manager is the interface to implement to authenticate users type Manager interface { + plugin.Plugin Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, map[string]*authpb.Scope, error) } diff --git a/pkg/auth/manager/appauth/appauth.go b/pkg/auth/manager/appauth/appauth.go index cb4b29a020..738f51b4cd 100644 --- a/pkg/auth/manager/appauth/appauth.go +++ b/pkg/auth/manager/appauth/appauth.go @@ -44,13 +44,21 @@ type manager struct { // New returns a new auth Manager. func New(m map[string]interface{}) (auth.Manager, error) { mgr := &manager{} - err := mapstructure.Decode(m, mgr) + err := mgr.Configure(m) if err != nil { - return nil, errors.Wrap(err, "error decoding conf") + return nil, err } return mgr, nil } +func (m *manager) Configure(ml map[string]interface{}) error { + err := mapstructure.Decode(ml, m) + if err != nil { + return errors.Wrap(err, "error decoding conf") + } + return nil +} + func (m *manager) Authenticate(ctx context.Context, username, password string) (*user.User, map[string]*authpb.Scope, error) { gtw, err := pool.GetGatewayServiceClient(m.GatewayAddr) if err != nil { diff --git a/pkg/auth/manager/demo/demo.go b/pkg/auth/manager/demo/demo.go index b0636f62ee..7f2e1ddd6d 100644 --- a/pkg/auth/manager/demo/demo.go +++ b/pkg/auth/manager/demo/demo.go @@ -46,8 +46,15 @@ type Credentials struct { // New returns a new auth Manager. func New(m map[string]interface{}) (auth.Manager, error) { // m not used + mgr := &manager{} + err := mgr.Configure(m) + return mgr, err +} + +func (m *manager) Configure(ml map[string]interface{}) error { creds := getCredentials() - return &manager{credentials: creds}, nil + m.credentials = creds + return nil } func (m *manager) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, map[string]*authpb.Scope, error) { diff --git a/pkg/auth/manager/impersonator/impersonator.go b/pkg/auth/manager/impersonator/impersonator.go index 0f9fd612ba..19d7f88000 100644 --- a/pkg/auth/manager/impersonator/impersonator.go +++ b/pkg/auth/manager/impersonator/impersonator.go @@ -40,6 +40,10 @@ func New(c map[string]interface{}) (auth.Manager, error) { return &mgr{}, nil } +func (m *mgr) Configure(ml map[string]interface{}) error { + return nil +} + func (m *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, map[string]*authpb.Scope, error) { // allow passing in uid as @ at := strings.LastIndex(clientID, "@") diff --git a/pkg/auth/manager/json/json.go b/pkg/auth/manager/json/json.go index e12c907126..daf81e25b3 100644 --- a/pkg/auth/manager/json/json.go +++ b/pkg/auth/manager/json/json.go @@ -79,30 +79,37 @@ func parseConfig(m map[string]interface{}) (*config, error) { // New returns a new auth Manager. func New(m map[string]interface{}) (auth.Manager, error) { - c, err := parseConfig(m) + mgr := &manager{} + err := mgr.Configure(m) if err != nil { return nil, err } + return mgr, nil +} - manager := &manager{credentials: map[string]*Credentials{}} +func (m *manager) Configure(ml map[string]interface{}) error { + c, err := parseConfig(ml) + if err != nil { + return err + } + m.credentials = map[string]*Credentials{} f, err := ioutil.ReadFile(c.Users) if err != nil { - return nil, err + return err } credentials := []*Credentials{} err = json.Unmarshal(f, &credentials) if err != nil { - return nil, err + return err } for _, c := range credentials { - manager.credentials[c.Username] = c + m.credentials[c.Username] = c } - - return manager, nil + return nil } func (m *manager) Authenticate(ctx context.Context, username string, secret string) (*user.User, map[string]*authpb.Scope, error) { diff --git a/pkg/auth/manager/ldap/ldap.go b/pkg/auth/manager/ldap/ldap.go index 1727076d0f..cefd1adef0 100644 --- a/pkg/auth/manager/ldap/ldap.go +++ b/pkg/auth/manager/ldap/ldap.go @@ -104,10 +104,19 @@ func parseConfig(m map[string]interface{}) (*config, error) { // New returns an auth manager implementation that connects to a LDAP server to validate the user. func New(m map[string]interface{}) (auth.Manager, error) { - c, err := parseConfig(m) + manager := &mgr{} + err := manager.Configure(m) if err != nil { return nil, err } + return manager, nil +} + +func (am *mgr) Configure(m map[string]interface{}) error { + c, err := parseConfig(m) + if err != nil { + return err + } // backwards compatibility if c.UserFilter != "" { @@ -122,10 +131,8 @@ func New(m map[string]interface{}) (auth.Manager, error) { } c.GatewaySvc = sharedconf.GetGatewaySVC(c.GatewaySvc) - - return &mgr{ - c: c, - }, nil + am.c = c + return nil } func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, map[string]*authpb.Scope, error) { diff --git a/pkg/auth/manager/oidc/oidc.go b/pkg/auth/manager/oidc/oidc.go index dd717bec85..c6dd62b371 100644 --- a/pkg/auth/manager/oidc/oidc.go +++ b/pkg/auth/manager/oidc/oidc.go @@ -79,13 +79,22 @@ func parseConfig(m map[string]interface{}) (*config, error) { // New returns an auth manager implementation that verifies the oidc token and obtains the user claims. func New(m map[string]interface{}) (auth.Manager, error) { - c, err := parseConfig(m) + manager := &mgr{} + err := manager.Configure(m) if err != nil { return nil, err } - c.init() + return manager, nil +} - return &mgr{c: c}, nil +func (am *mgr) Configure(m map[string]interface{}) error { + c, err := parseConfig(m) + if err != nil { + return err + } + c.init() + am.c = c + return nil } // the clientID it would be empty as we only need to validate the clientSecret variable diff --git a/pkg/auth/manager/publicshares/publicshares.go b/pkg/auth/manager/publicshares/publicshares.go index ceed142111..c00f37f996 100644 --- a/pkg/auth/manager/publicshares/publicshares.go +++ b/pkg/auth/manager/publicshares/publicshares.go @@ -61,14 +61,21 @@ func parseConfig(m map[string]interface{}) (*config, error) { // New returns a new auth Manager. func New(m map[string]interface{}) (auth.Manager, error) { - conf, err := parseConfig(m) + mgr := &manager{} + err := mgr.Configure(m) if err != nil { return nil, err } + return mgr, nil +} - return &manager{ - c: conf, - }, nil +func (m *manager) Configure(ml map[string]interface{}) error { + conf, err := parseConfig(ml) + if err != nil { + return err + } + m.c = conf + return nil } func (m *manager) Authenticate(ctx context.Context, token, secret string) (*user.User, map[string]*authpb.Scope, error) { diff --git a/pkg/auth/rpc_auth.go b/pkg/auth/rpc_auth.go new file mode 100644 index 0000000000..92dbee5bb4 --- /dev/null +++ b/pkg/auth/rpc_auth.go @@ -0,0 +1,118 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package auth + +import ( + "context" + "net/rpc" + + authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" + user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/plugin" + hcplugin "github.com/hashicorp/go-plugin" +) + +func init() { + plugin.Register("authprovider", &ProviderPlugin{}) +} + +// ProviderPlugin is the implementation of plugin.Plugin so we can serve/consume this. +type ProviderPlugin struct { + Impl Manager +} + +// Server returns the RPC Server which serves the methods that the Client calls over net/rpc +func (p *ProviderPlugin) Server(*hcplugin.MuxBroker) (interface{}, error) { + return &RPCServer{Impl: p.Impl}, nil +} + +// Client returns interface implementation for the plugin that communicates to the server end of the plugin +func (p *ProviderPlugin) Client(b *hcplugin.MuxBroker, c *rpc.Client) (interface{}, error) { + return &RPCClient{Client: c}, nil +} + +// RPCClient is an implementation of Manager that talks over RPC. +type RPCClient struct{ Client *rpc.Client } + +// ConfigureArg for RPC +type ConfigureArg struct { + Ml map[string]interface{} +} + +// ConfigureReply for RPC +type ConfigureReply struct { + Err error +} + +// Configure RPCClient configure method +func (m *RPCClient) Configure(ml map[string]interface{}) error { + args := ConfigureArg{Ml: ml} + resp := ConfigureReply{} + err := m.Client.Call("Plugin.Configure", args, &resp) + if err != nil { + return err + } + return resp.Err +} + +// AuthenticateArgs for RPC +type AuthenticateArgs struct { + Ctx map[interface{}]interface{} + ClientID string + ClientSecret string +} + +// AuthenticateReply for RPC +type AuthenticateReply struct { + User *user.User + Auth map[string]*authpb.Scope + Error error +} + +// Authenticate RPCClient Authenticate method +func (m *RPCClient) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, map[string]*authpb.Scope, error) { + ctxVal := appctx.GetKeyValuesFromCtx(ctx) + args := AuthenticateArgs{Ctx: ctxVal, ClientID: clientID, ClientSecret: clientSecret} + reply := AuthenticateReply{} + err := m.Client.Call("Plugin.Authenticate", args, &reply) + if err != nil { + return nil, nil, err + } + return reply.User, reply.Auth, reply.Error +} + +// RPCServer is the server that RPCClient talks to, conforming to the requirements of net/rpc +type RPCServer struct { + // This is the real implementation + Impl Manager +} + +// Configure RPCServer Configure method +func (m *RPCServer) Configure(args ConfigureArg, resp *ConfigureReply) error { + resp.Err = m.Impl.Configure(args.Ml) + return nil +} + +// Authenticate RPCServer Authenticate method +func (m *RPCServer) Authenticate(args AuthenticateArgs, resp *AuthenticateReply) error { + ctx := appctx.PutKeyValuesToCtx(args.Ctx) + resp.User, resp.Auth, resp.Error = m.Impl.Authenticate(ctx, args.ClientID, args.ClientSecret) + return nil +} diff --git a/pkg/cbox/user/rest/rest.go b/pkg/cbox/user/rest/rest.go index 348c8f00a4..eeedaba59a 100644 --- a/pkg/cbox/user/rest/rest.go +++ b/pkg/cbox/user/rest/rest.go @@ -106,19 +106,26 @@ func parseConfig(m map[string]interface{}) (*config, error) { // New returns a user manager implementation that makes calls to the GRAPPA API. func New(m map[string]interface{}) (user.Manager, error) { - c, err := parseConfig(m) + mgr := &manager{} + err := mgr.Configure(m) if err != nil { return nil, err } - c.init() + return mgr, err +} +func (m *manager) Configure(ml map[string]interface{}) error { + c, err := parseConfig(ml) + if err != nil { + return err + } + c.init() redisPool := initRedisPool(c.RedisAddress, c.RedisUsername, c.RedisPassword) apiTokenManager := utils.InitAPITokenManager(c.TargetAPI, c.OIDCTokenEndpoint, c.ClientID, c.ClientSecret) - return &manager{ - conf: c, - redisPool: redisPool, - apiTokenManager: apiTokenManager, - }, nil + m.conf = c + m.redisPool = redisPool + m.apiTokenManager = apiTokenManager + return nil } func (m *manager) getUserByParam(ctx context.Context, param, val string) (map[string]interface{}, error) { diff --git a/pkg/plugin/loader.go b/pkg/plugin/loader.go new file mode 100644 index 0000000000..335af6275f --- /dev/null +++ b/pkg/plugin/loader.go @@ -0,0 +1,127 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package plugin + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "path/filepath" + "regexp" + + "github.com/cs3org/reva/pkg/errtypes" + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-plugin" +) + +// RevaPlugin represents the runtime plugin +type RevaPlugin struct { + Plugin interface{} + Client *plugin.Client +} + +const dirname = "/var/tmp/reva" + +var isAlphaNum = regexp.MustCompile(`^[A-Za-z0-9]+$`).MatchString + +// Kill kills the plugin process +func (plug *RevaPlugin) Kill() { + plug.Client.Kill() +} + +var handshake = plugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "BASIC_PLUGIN", + MagicCookieValue: "hello", +} + +func compile(pluginType string, path string) (string, error) { + var errb bytes.Buffer + binaryPath := filepath.Join(dirname, "bin", pluginType, filepath.Base(path)) + command := fmt.Sprintf("go build -o %s %s", binaryPath, path) + cmd := exec.Command("bash", "-c", command) + cmd.Stderr = &errb + err := cmd.Run() + if err != nil { + return "", fmt.Errorf("%v: %w", errb.String(), err) + } + return binaryPath, nil +} + +// checkDir checks and compiles plugin if the configuration points to a directory. +func checkDirAndCompile(pluginType, driver string) (string, error) { + bin := driver + file, err := os.Stat(driver) + if err != nil { + return "", err + } + // compile if we point to a package + if file.IsDir() { + bin, err = compile(pluginType, driver) + if err != nil { + return "", err + } + } + return bin, nil +} + +// Load loads the plugin using the hashicorp go-plugin system +func Load(pluginType, driver string) (*RevaPlugin, error) { + if isAlphaNum(driver) { + return nil, errtypes.NotFound(driver) + } + bin, err := checkDirAndCompile(pluginType, driver) + if err != nil { + return nil, err + } + + logger := hclog.New(&hclog.LoggerOptions{ + Name: "plugin", + Output: os.Stdout, + Level: hclog.Trace, + }) + + client := plugin.NewClient(&plugin.ClientConfig{ + HandshakeConfig: handshake, + Plugins: PluginMap, + Cmd: exec.Command(bin), + AllowedProtocols: []plugin.Protocol{ + plugin.ProtocolNetRPC, + }, + Logger: logger, + }) + + rpcClient, err := client.Client() + if err != nil { + return nil, err + } + + raw, err := rpcClient.Dispense(pluginType) + if err != nil { + return nil, err + } + + revaPlugin := &RevaPlugin{ + Plugin: raw, + Client: client, + } + + return revaPlugin, nil +} diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go new file mode 100644 index 0000000000..47fc5b50ce --- /dev/null +++ b/pkg/plugin/plugin.go @@ -0,0 +1,24 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package plugin + +// Plugin is the interface used to configure plugins +type Plugin interface { + Configure(m map[string]interface{}) error +} diff --git a/pkg/plugin/registry.go b/pkg/plugin/registry.go new file mode 100644 index 0000000000..43894f1921 --- /dev/null +++ b/pkg/plugin/registry.go @@ -0,0 +1,29 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package plugin + +import "github.com/hashicorp/go-plugin" + +// PluginMap is a map containing all the plugins +var PluginMap = map[string]plugin.Plugin{} + +// Register registers the plugin +func Register(name string, plugin plugin.Plugin) { + PluginMap[name] = plugin +} diff --git a/pkg/user/manager/demo/demo.go b/pkg/user/manager/demo/demo.go index c77d1117dc..68672917b7 100644 --- a/pkg/user/manager/demo/demo.go +++ b/pkg/user/manager/demo/demo.go @@ -40,8 +40,18 @@ type manager struct { // New returns a new user manager. func New(m map[string]interface{}) (user.Manager, error) { + mgr := &manager{} + err := mgr.Configure(m) + if err != nil { + return nil, err + } + return mgr, err +} + +func (m *manager) Configure(ml map[string]interface{}) error { cat := getUsers() - return &manager{catalog: cat}, nil + m.catalog = cat + return nil } func (m *manager) GetUser(ctx context.Context, uid *userpb.UserId) (*userpb.User, error) { diff --git a/pkg/user/manager/json/json.go b/pkg/user/manager/json/json.go index 3e7db910f1..0966ae2349 100644 --- a/pkg/user/manager/json/json.go +++ b/pkg/user/manager/json/json.go @@ -65,26 +65,33 @@ func parseConfig(m map[string]interface{}) (*config, error) { // New returns a user manager implementation that reads a json file to provide user metadata. func New(m map[string]interface{}) (user.Manager, error) { - c, err := parseConfig(m) + mgr := &manager{} + err := mgr.Configure(m) if err != nil { return nil, err } + return mgr, nil +} + +func (m *manager) Configure(ml map[string]interface{}) error { + c, err := parseConfig(ml) + if err != nil { + return err + } f, err := ioutil.ReadFile(c.Users) if err != nil { - return nil, err + return err } users := []*userpb.User{} err = json.Unmarshal(f, &users) if err != nil { - return nil, err + return err } - - return &manager{ - users: users, - }, nil + m.users = users + return nil } func (m *manager) GetUser(ctx context.Context, uid *userpb.UserId) (*userpb.User, error) { diff --git a/pkg/user/manager/ldap/ldap.go b/pkg/user/manager/ldap/ldap.go index 133a4f57bc..575bfcd0d3 100644 --- a/pkg/user/manager/ldap/ldap.go +++ b/pkg/user/manager/ldap/ldap.go @@ -105,10 +105,19 @@ func parseConfig(m map[string]interface{}) (*config, error) { // New returns a user manager implementation that connects to a LDAP server to provide user metadata. func New(m map[string]interface{}) (user.Manager, error) { - c, err := parseConfig(m) + mgr := &manager{} + err := mgr.Configure(m) if err != nil { return nil, err } + return mgr, nil +} + +func (m *manager) Configure(ml map[string]interface{}) error { + c, err := parseConfig(ml) + if err != nil { + return err + } // backwards compatibility c.UserFilter = strings.ReplaceAll(c.UserFilter, "%s", "{{.OpaqueId}}") @@ -121,22 +130,18 @@ func New(m map[string]interface{}) (user.Manager, error) { c.Nobody = 99 } - mgr := &manager{ - c: c, - } - - mgr.userfilter, err = template.New("uf").Funcs(sprig.TxtFuncMap()).Parse(c.UserFilter) + m.c = c + m.userfilter, err = template.New("uf").Funcs(sprig.TxtFuncMap()).Parse(c.UserFilter) if err != nil { err := errors.Wrap(err, fmt.Sprintf("error parsing userfilter tpl:%s", c.UserFilter)) panic(err) } - mgr.groupfilter, err = template.New("gf").Funcs(sprig.TxtFuncMap()).Parse(c.GroupFilter) + m.groupfilter, err = template.New("gf").Funcs(sprig.TxtFuncMap()).Parse(c.GroupFilter) if err != nil { err := errors.Wrap(err, fmt.Sprintf("error parsing groupfilter tpl:%s", c.GroupFilter)) panic(err) } - - return mgr, nil + return nil } func (m *manager) GetUser(ctx context.Context, uid *userpb.UserId) (*userpb.User, error) { diff --git a/pkg/user/rpc_user.go b/pkg/user/rpc_user.go new file mode 100644 index 0000000000..ec7946cad0 --- /dev/null +++ b/pkg/user/rpc_user.go @@ -0,0 +1,211 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package user + +import ( + "context" + "encoding/gob" + "net/rpc" + + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/plugin" + hcplugin "github.com/hashicorp/go-plugin" +) + +func init() { + gob.Register(&userpb.User{}) + plugin.Register("userprovider", &ProviderPlugin{}) +} + +// ProviderPlugin is the implementation of plugin.Plugin so we can serve/consume this. +type ProviderPlugin struct { + Impl Manager +} + +// Server returns the RPC Server which serves the methods that the Client calls over net/rpc +func (p *ProviderPlugin) Server(*hcplugin.MuxBroker) (interface{}, error) { + return &RPCServer{Impl: p.Impl}, nil +} + +// Client returns interface implementation for the plugin that communicates to the server end of the plugin +func (p *ProviderPlugin) Client(b *hcplugin.MuxBroker, c *rpc.Client) (interface{}, error) { + return &RPCClient{Client: c}, nil +} + +// RPCClient is an implementation of Manager that talks over RPC. +type RPCClient struct{ Client *rpc.Client } + +// ConfigureArg for RPC +type ConfigureArg struct { + Ml map[string]interface{} +} + +// ConfigureReply for RPC +type ConfigureReply struct { + Err error +} + +// Configure RPCClient configure method +func (m *RPCClient) Configure(ml map[string]interface{}) error { + args := ConfigureArg{Ml: ml} + resp := ConfigureReply{} + err := m.Client.Call("Plugin.Configure", args, &resp) + if err != nil { + return err + } + return resp.Err +} + +// GetUserArg for RPC +type GetUserArg struct { + Ctx map[interface{}]interface{} + UID *userpb.UserId +} + +// GetUserReply for RPC +type GetUserReply struct { + User *userpb.User + Err error +} + +// GetUser RPCClient GetUser method +func (m *RPCClient) GetUser(ctx context.Context, uid *userpb.UserId) (*userpb.User, error) { + ctxVal := appctx.GetKeyValuesFromCtx(ctx) + args := GetUserArg{Ctx: ctxVal, UID: uid} + resp := GetUserReply{} + err := m.Client.Call("Plugin.GetUser", args, &resp) + if err != nil { + return nil, err + } + return resp.User, resp.Err +} + +// GetUserByClaimArg for RPC +type GetUserByClaimArg struct { + Ctx map[interface{}]interface{} + Claim string + Value string +} + +// GetUserByClaimReply for RPC +type GetUserByClaimReply struct { + User *userpb.User + Err error +} + +// GetUserByClaim RPCClient GetUserByClaim method +func (m *RPCClient) GetUserByClaim(ctx context.Context, claim, value string) (*userpb.User, error) { + ctxVal := appctx.GetKeyValuesFromCtx(ctx) + args := GetUserByClaimArg{Ctx: ctxVal, Claim: claim, Value: value} + resp := GetUserByClaimReply{} + err := m.Client.Call("Plugin.GetUserByClaim", args, &resp) + if err != nil { + return nil, err + } + return resp.User, resp.Err +} + +// GetUserGroupsArg for RPC +type GetUserGroupsArg struct { + Ctx map[interface{}]interface{} + User *userpb.UserId +} + +// GetUserGroupsReply for RPC +type GetUserGroupsReply struct { + Group []string + Err error +} + +// GetUserGroups RPCClient GetUserGroups method +func (m *RPCClient) GetUserGroups(ctx context.Context, user *userpb.UserId) ([]string, error) { + ctxVal := appctx.GetKeyValuesFromCtx(ctx) + args := GetUserGroupsArg{Ctx: ctxVal, User: user} + resp := GetUserGroupsReply{} + err := m.Client.Call("Plugin.GetUserGroups", args, &resp) + if err != nil { + return nil, err + } + return resp.Group, resp.Err +} + +// FindUsersArg for RPC +type FindUsersArg struct { + Ctx map[interface{}]interface{} + Query string +} + +// FindUsersReply for RPC +type FindUsersReply struct { + User []*userpb.User + Err error +} + +// FindUsers RPCClient FindUsers method +func (m *RPCClient) FindUsers(ctx context.Context, query string) ([]*userpb.User, error) { + ctxVal := appctx.GetKeyValuesFromCtx(ctx) + args := FindUsersArg{Ctx: ctxVal, Query: query} + resp := FindUsersReply{} + err := m.Client.Call("Plugin.FindUsers", args, &resp) + if err != nil { + return nil, err + } + return resp.User, resp.Err +} + +// RPCServer is the server that RPCClient talks to, conforming to the requirements of net/rpc +type RPCServer struct { + // This is the real implementation + Impl Manager +} + +// Configure RPCServer Configure method +func (m *RPCServer) Configure(args ConfigureArg, resp *ConfigureReply) error { + resp.Err = m.Impl.Configure(args.Ml) + return nil +} + +// GetUser RPCServer GetUser method +func (m *RPCServer) GetUser(args GetUserArg, resp *GetUserReply) error { + ctx := appctx.PutKeyValuesToCtx(args.Ctx) + resp.User, resp.Err = m.Impl.GetUser(ctx, args.UID) + return nil +} + +// GetUserByClaim RPCServer GetUserByClaim method +func (m *RPCServer) GetUserByClaim(args GetUserByClaimArg, resp *GetUserByClaimReply) error { + ctx := appctx.PutKeyValuesToCtx(args.Ctx) + resp.User, resp.Err = m.Impl.GetUserByClaim(ctx, args.Claim, args.Value) + return nil +} + +// GetUserGroups RPCServer GetUserGroups method +func (m *RPCServer) GetUserGroups(args GetUserGroupsArg, resp *GetUserGroupsReply) error { + ctx := appctx.PutKeyValuesToCtx(args.Ctx) + resp.Group, resp.Err = m.Impl.GetUserGroups(ctx, args.User) + return nil +} + +// FindUsers RPCServer FindUsers method +func (m *RPCServer) FindUsers(args FindUsersArg, resp *FindUsersReply) error { + ctx := appctx.PutKeyValuesToCtx(args.Ctx) + resp.User, resp.Err = m.Impl.FindUsers(ctx, args.Query) + return nil +} diff --git a/pkg/user/user.go b/pkg/user/user.go index 14be317c6b..d534ce7e93 100644 --- a/pkg/user/user.go +++ b/pkg/user/user.go @@ -22,6 +22,7 @@ import ( "context" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + "github.com/cs3org/reva/pkg/plugin" ) type key int @@ -64,6 +65,7 @@ func ContextSetUserID(ctx context.Context, id *userpb.UserId) context.Context { // Manager is the interface to implement to manipulate users. type Manager interface { + plugin.Plugin GetUser(ctx context.Context, uid *userpb.UserId) (*userpb.User, error) GetUserByClaim(ctx context.Context, claim, value string) (*userpb.User, error) GetUserGroups(ctx context.Context, uid *userpb.UserId) ([]string, error)