diff --git a/ocs/go.mod b/ocs/go.mod index 0f60b427ad..cfa6dd54bc 100644 --- a/ocs/go.mod +++ b/ocs/go.mod @@ -18,6 +18,7 @@ require ( github.com/olekukonko/tablewriter v0.0.5 github.com/openzipkin/zipkin-go v0.2.5 github.com/owncloud/ocis/accounts v0.5.3-0.20210216094451-dc73176dc62d + github.com/owncloud/ocis/proxy v0.0.0-20210216094451-dc73176dc62d github.com/owncloud/ocis/ocis-pkg v0.0.0-20210216094451-dc73176dc62d github.com/owncloud/ocis/settings v0.0.0-20210216094451-dc73176dc62d github.com/owncloud/ocis/store v0.0.0-20210216094451-dc73176dc62d @@ -32,6 +33,7 @@ require ( replace ( github.com/owncloud/ocis/accounts => ../accounts + github.com/owncloud/ocis/proxy => ../proxy github.com/owncloud/ocis/ocis-pkg => ../ocis-pkg github.com/owncloud/ocis/settings => ../settings github.com/owncloud/ocis/store => ../store diff --git a/ocs/pkg/config/config.go b/ocs/pkg/config/config.go index 6a01ad55d6..451701210a 100644 --- a/ocs/pkg/config/config.go +++ b/ocs/pkg/config/config.go @@ -47,13 +47,15 @@ type TokenManager struct { // Config combines all available configuration parts. type Config struct { - File string - Log Log - Debug Debug - HTTP HTTP - Tracing Tracing - TokenManager TokenManager - Service Service + File string + Log Log + Debug Debug + HTTP HTTP + Tracing Tracing + TokenManager TokenManager + Service Service + AccountBackend string + RevaAddress string Context context.Context Supervised bool diff --git a/ocs/pkg/flagset/flagset.go b/ocs/pkg/flagset/flagset.go index 7e428536db..d1924af673 100644 --- a/ocs/pkg/flagset/flagset.go +++ b/ocs/pkg/flagset/flagset.go @@ -150,6 +150,21 @@ func ServerWithConfig(cfg *config.Config) []cli.Flag { EnvVars: []string{"OCS_JWT_SECRET", "OCIS_JWT_SECRET"}, Destination: &cfg.TokenManager.JWTSecret, }, + + &cli.StringFlag{ + Name: "account-backend-type", + Value: flags.OverrideDefaultString(cfg.AccountBackend, "accounts"), + Usage: "account-backend-type", + EnvVars: []string{"OCS_ACCOUNT_BACKEND_TYPE"}, + Destination: &cfg.AccountBackend, + }, + &cli.StringFlag{ + Name: "reva-gateway-addr", + Value: flags.OverrideDefaultString(cfg.RevaAddress, "127.0.0.1:9142"), + Usage: "REVA Gateway Endpoint", + EnvVars: []string{"OCS_REVA_GATEWAY_ADDR"}, + Destination: &cfg.RevaAddress, + }, } } diff --git a/ocs/pkg/service/v0/service.go b/ocs/pkg/service/v0/service.go index 09d559c9f5..e5a882455f 100644 --- a/ocs/pkg/service/v0/service.go +++ b/ocs/pkg/service/v0/service.go @@ -19,6 +19,8 @@ import ( ocsm "github.com/owncloud/ocis/ocs/pkg/middleware" "github.com/owncloud/ocis/ocs/pkg/service/v0/data" "github.com/owncloud/ocis/ocs/pkg/service/v0/response" + "github.com/owncloud/ocis/proxy/pkg/cs3" + "github.com/owncloud/ocis/proxy/pkg/user/backend" settings "github.com/owncloud/ocis/settings/pkg/proto/v0" ) @@ -57,6 +59,10 @@ func NewService(opts ...Option) Service { logger: options.Logger, } + if svc.config.AccountBackend == "" { + svc.config.AccountBackend = "accounts" + } + requireUser := ocsm.RequireUser() requireAdmin := ocsm.RequireAdmin( @@ -154,6 +160,14 @@ func (o Ocs) getAccountService() accounts.AccountsService { return accounts.NewAccountsService("com.owncloud.api.accounts", grpc.DefaultClient) } +func (o Ocs) getCS3Backend() backend.UserBackend { + revaClient, err := cs3.GetGatewayServiceClient(o.config.RevaAddress) + if err != nil { + o.logger.Fatal().Msgf("could not get reva client at address %s", o.config.RevaAddress) + } + return backend.NewCS3UserBackend(revaClient, nil, revaClient, o.logger) +} + func (o Ocs) getGroupsService() accounts.GroupsService { return accounts.NewGroupsService("com.owncloud.api.accounts", grpc.DefaultClient) } diff --git a/ocs/pkg/service/v0/users.go b/ocs/pkg/service/v0/users.go index d01c83f090..ac25d0541d 100644 --- a/ocs/pkg/service/v0/users.go +++ b/ocs/pkg/service/v0/users.go @@ -17,6 +17,7 @@ import ( "google.golang.org/protobuf/types/known/fieldmaskpb" merrors "github.com/asim/go-micro/v3/errors" + cs3 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" accounts "github.com/owncloud/ocis/accounts/pkg/proto/v0" "github.com/owncloud/ocis/ocs/pkg/service/v0/data" "github.com/owncloud/ocis/ocs/pkg/service/v0/response" @@ -42,13 +43,14 @@ func (o Ocs) GetSelf(w http.ResponseWriter, r *http.Request) { // TODO(someone) this fix is in place because if the user backend (PROXY_ACCOUNT_BACKEND_TYPE) is set to, for instance, // cs3, we cannot count with the accounts service. if u != nil { + uid, gid := o.extractUIDAndGID(u) d := &data.User{ UserID: u.Username, DisplayName: u.DisplayName, LegacyDisplayName: u.DisplayName, Email: u.Mail, - UIDNumber: u.UidNumber, - GIDNumber: u.GidNumber, + UIDNumber: uid, + GIDNumber: gid, } mustNotFail(render.Render(w, r, response.DataRender(d))) return @@ -81,11 +83,17 @@ func (o Ocs) GetUser(w http.ResponseWriter, r *http.Request) { var account *accounts.Account var err error - if userid == "" { + switch { + case userid == "": mustNotFail(render.Render(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "missing user in context"))) - } else { + case o.config.AccountBackend == "accounts": account, err = o.fetchAccountByUsername(r.Context(), userid) + case o.config.AccountBackend == "cs3": + account, err = o.fetchAccountFromCS3Backend(r.Context(), userid) + default: + o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) } + if err != nil { merr := merrors.FromError(err) if merr.Code == http.StatusNotFound { @@ -188,9 +196,19 @@ func (o Ocs) AddUser(w http.ResponseWriter, r *http.Request) { newAccount.GidNumber = gidNumber } - account, err := o.getAccountService().CreateAccount(r.Context(), &accounts.CreateAccountRequest{ - Account: newAccount, - }) + var account *accounts.Account + + switch o.config.AccountBackend { + case "accounts": + account, err = o.getAccountService().CreateAccount(r.Context(), &accounts.CreateAccountRequest{ + Account: newAccount, + }) + case "cs3": + o.logger.Fatal().Msg("cs3 backend doesn't support adding users") + default: + o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) + } + if err != nil { merr := merrors.FromError(err) switch merr.Code { @@ -239,7 +257,18 @@ func (o Ocs) AddUser(w http.ResponseWriter, r *http.Request) { // EditUser creates a new user account func (o Ocs) EditUser(w http.ResponseWriter, r *http.Request) { userid := chi.URLParam(r, "userid") - account, err := o.fetchAccountByUsername(r.Context(), userid) + + var account *accounts.Account + var err error + switch o.config.AccountBackend { + case "accounts": + account, err = o.fetchAccountByUsername(r.Context(), userid) + case "cs3": + o.logger.Fatal().Msg("cs3 backend doesn't support editing users") + default: + o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) + } + if err != nil { merr := merrors.FromError(err) if merr.Code == http.StatusNotFound { @@ -306,7 +335,18 @@ func (o Ocs) EditUser(w http.ResponseWriter, r *http.Request) { // DeleteUser deletes a user func (o Ocs) DeleteUser(w http.ResponseWriter, r *http.Request) { userid := chi.URLParam(r, "userid") - account, err := o.fetchAccountByUsername(r.Context(), userid) + + var account *accounts.Account + var err error + switch o.config.AccountBackend { + case "accounts": + account, err = o.fetchAccountByUsername(r.Context(), userid) + case "cs3": + o.logger.Fatal().Msg("cs3 backend doesn't support deleting users") + default: + o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) + } + if err != nil { merr := merrors.FromError(err) if merr.Code == http.StatusNotFound { @@ -341,7 +381,18 @@ func (o Ocs) DeleteUser(w http.ResponseWriter, r *http.Request) { // EnableUser enables a user func (o Ocs) EnableUser(w http.ResponseWriter, r *http.Request) { userid := chi.URLParam(r, "userid") - account, err := o.fetchAccountByUsername(r.Context(), userid) + + var account *accounts.Account + var err error + switch o.config.AccountBackend { + case "accounts": + account, err = o.fetchAccountByUsername(r.Context(), userid) + case "cs3": + o.logger.Fatal().Msg("cs3 backend doesn't support enabling users") + default: + o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) + } + if err != nil { merr := merrors.FromError(err) if merr.Code == http.StatusNotFound { @@ -381,7 +432,18 @@ func (o Ocs) EnableUser(w http.ResponseWriter, r *http.Request) { // DisableUser disables a user func (o Ocs) DisableUser(w http.ResponseWriter, r *http.Request) { userid := chi.URLParam(r, "userid") - account, err := o.fetchAccountByUsername(r.Context(), userid) + + var account *accounts.Account + var err error + switch o.config.AccountBackend { + case "accounts": + account, err = o.fetchAccountByUsername(r.Context(), userid) + case "cs3": + o.logger.Fatal().Msg("cs3 backend doesn't support disabling users") + default: + o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) + } + if err != nil { merr := merrors.FromError(err) if merr.Code == http.StatusNotFound { @@ -498,9 +560,20 @@ func (o Ocs) ListUsers(w http.ResponseWriter, r *http.Request) { query = fmt.Sprintf("on_premises_sam_account_name eq '%s'", escapeValue(search)) } - res, err := o.getAccountService().ListAccounts(r.Context(), &accounts.ListAccountsRequest{ - Query: query, - }) + var res *accounts.ListAccountsResponse + var err error + switch o.config.AccountBackend { + case "accounts": + res, err = o.getAccountService().ListAccounts(r.Context(), &accounts.ListAccountsRequest{ + Query: query, + }) + case "cs3": + // TODO + o.logger.Fatal().Msg("cs3 backend doesn't support listing users") + default: + o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) + } + if err != nil { o.logger.Err(err).Msg("could not list users") mustNotFail(render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, "could not list users"))) @@ -533,3 +606,43 @@ func (o Ocs) fetchAccountByUsername(ctx context.Context, name string) (*accounts } return nil, merrors.NotFound("", "The requested user could not be found") } + +func (o Ocs) fetchAccountFromCS3Backend(ctx context.Context, name string) (*accounts.Account, error) { + backend := o.getCS3Backend() + u, err := backend.GetUserByClaims(ctx, "username", name, false) + if err != nil { + return nil, err + } + uid, gid := o.extractUIDAndGID(u) + return &accounts.Account{ + OnPremisesSamAccountName: u.Username, + DisplayName: u.DisplayName, + Mail: u.Mail, + UidNumber: uid, + GidNumber: gid, + }, nil +} + +func (o Ocs) extractUIDAndGID(u *cs3.User) (int64, int64) { + var uid, gid int64 + var err error + if u.Opaque != nil && u.Opaque.Map != nil { + if uidObj, ok := u.Opaque.Map["uid"]; ok { + if uidObj.Decoder == "plain" { + uid, err = strconv.ParseInt(string(uidObj.Value), 10, 64) + if err != nil { + o.logger.Error().Err(err).Interface("user", u).Msg("could not extract uid for user") + } + } + } + if gidObj, ok := u.Opaque.Map["gid"]; ok { + if gidObj.Decoder == "plain" { + gid, err = strconv.ParseInt(string(gidObj.Value), 10, 64) + if err != nil { + o.logger.Error().Err(err).Interface("user", u).Msg("could not extract gid for user") + } + } + } + } + return uid, gid +} diff --git a/proxy/pkg/user/backend/cs3.go b/proxy/pkg/user/backend/cs3.go index 3c97d36427..3dff9c88c9 100644 --- a/proxy/pkg/user/backend/cs3.go +++ b/proxy/pkg/user/backend/cs3.go @@ -70,10 +70,14 @@ func (c *cs3backend) GetUserByClaims(ctx context.Context, claim, value string, w c.logger.Error().Err(err).Msg("Could not encode loaded roles") } - user.Opaque = &types.Opaque{ - Map: map[string]*types.OpaqueEntry{ - "roles": enc, - }, + if user.Opaque == nil { + user.Opaque = &types.Opaque{ + Map: map[string]*types.OpaqueEntry{ + "roles": enc, + }, + } + } else { + user.Opaque.Map["roles"] = enc } return res.User, nil