Add 'accounts/' from commit 'd2585070bc25d2c8cd0c86476041ee502afb4ac5'

git-subtree-dir: accounts
git-subtree-mainline: 7b8e9bc298
git-subtree-split: d2585070bc
This commit is contained in:
A.Unger
2020-09-18 12:21:51 +02:00
138 changed files with 26531 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
package command
import (
"fmt"
"github.com/micro/cli/v2"
"github.com/micro/go-micro/v2/client/grpc"
"github.com/owncloud/ocis-accounts/pkg/config"
"github.com/owncloud/ocis-accounts/pkg/flagset"
accounts "github.com/owncloud/ocis-accounts/pkg/proto/v0"
)
// AddAccount command creates a new account
func AddAccount(cfg *config.Config) *cli.Command {
a := &accounts.Account{
PasswordProfile: &accounts.PasswordProfile{},
}
return &cli.Command{
Name: "add",
Usage: "Create a new account",
Aliases: []string{"create", "a"},
Flags: flagset.AddAccountWithConfig(cfg, a),
Before: func(c *cli.Context) error {
// Write value of username to the flags beneath, as preferred name
// and on-premises-sam-account-name is probably confusing for users.
if username := c.String("username"); username != "" {
if !c.IsSet("on-premises-sam-account-name") {
if err := c.Set("on-premises-sam-account-name", username); err != nil {
return err
}
}
if !c.IsSet("preferred-name") {
if err := c.Set("preferred-name", username); err != nil {
return err
}
}
}
return nil
},
Action: func(c *cli.Context) error {
accSvcID := cfg.GRPC.Namespace + "." + cfg.Server.Name
accSvc := accounts.NewAccountsService(accSvcID, grpc.NewClient())
_, err := accSvc.CreateAccount(c.Context, &accounts.CreateAccountRequest{
Account: a,
})
if err != nil {
fmt.Println(fmt.Errorf("could not create account %w", err))
return err
}
return nil
}}
}
+76
View File
@@ -0,0 +1,76 @@
package command
import (
"fmt"
"github.com/micro/cli/v2"
"github.com/micro/go-micro/v2/client/grpc"
tw "github.com/olekukonko/tablewriter"
"github.com/owncloud/ocis-accounts/pkg/config"
"github.com/owncloud/ocis-accounts/pkg/flagset"
accounts "github.com/owncloud/ocis-accounts/pkg/proto/v0"
"os"
"strconv"
)
// InspectAccount command shows detailed information about a specific account.
func InspectAccount(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "inspect",
Usage: "Show detailed data on an existing account",
ArgsUsage: "id",
Flags: flagset.InspectAccountWithConfig(cfg),
Action: func(c *cli.Context) error {
accServiceID := cfg.GRPC.Namespace + "." + cfg.Server.Name
if c.NArg() != 1 {
fmt.Println("Please provide a user-id")
os.Exit(1)
}
uid := c.Args().First()
accSvc := accounts.NewAccountsService(accServiceID, grpc.NewClient())
acc, err := accSvc.GetAccount(c.Context, &accounts.GetAccountRequest{
Id: uid,
})
if err != nil {
fmt.Println(fmt.Errorf("could not view account %w", err))
return err
}
buildAccountInspectTable(acc).Render()
return nil
}}
}
func buildAccountInspectTable(acc *accounts.Account) *tw.Table {
table := tw.NewWriter(os.Stdout)
table.SetAutoMergeCells(true)
table.AppendBulk([][]string{
{"ID", acc.Id},
{"Mail", acc.Mail},
{"DisplayName", acc.DisplayName},
{"PreferredName", acc.PreferredName},
{"AccountEnabled", strconv.FormatBool(acc.AccountEnabled)},
{"CreationType", acc.CreationType},
{"CreatedDateTime", acc.CreatedDateTime.String()},
{"Description", acc.Description},
{"ExternalUserState", acc.ExternalUserState},
{"UidNumber", fmt.Sprintf("%+d", acc.UidNumber)},
{"GidNumber", fmt.Sprintf("%+d", acc.GidNumber)},
{"IsResourceAccount", strconv.FormatBool(acc.IsResourceAccount)},
{"OnPremisesDistinguishedName", acc.OnPremisesDistinguishedName},
{"OnPremisesDomainName", acc.OnPremisesDomainName},
{"OnPremisesImmutableId", acc.OnPremisesImmutableId},
{"OnPremisesSamAccountName", acc.OnPremisesSamAccountName},
{"OnPremisesSecurityIdentifier", acc.OnPremisesSecurityIdentifier},
{"OnPremisesUserPrincipalName", acc.OnPremisesUserPrincipalName},
{"RefreshTokenValidFromDateTime", acc.RefreshTokensValidFromDateTime.String()},
})
// Merged cell with group memberships
for k := range acc.MemberOf {
table.Append([]string{"MemberOf", acc.MemberOf[k].DisplayName})
}
return table
}
+50
View File
@@ -0,0 +1,50 @@
package command
import (
"fmt"
"github.com/micro/cli/v2"
"github.com/micro/go-micro/v2/client/grpc"
tw "github.com/olekukonko/tablewriter"
"github.com/owncloud/ocis-accounts/pkg/config"
"github.com/owncloud/ocis-accounts/pkg/flagset"
accounts "github.com/owncloud/ocis-accounts/pkg/proto/v0"
"os"
"strconv"
)
// ListAccounts command lists all accounts
func ListAccounts(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "list",
Usage: "List existing accounts",
Aliases: []string{"ls"},
Flags: flagset.ListAccountsWithConfig(cfg),
Action: func(c *cli.Context) error {
accSvcID := cfg.GRPC.Namespace + "." + cfg.Server.Name
accSvc := accounts.NewAccountsService(accSvcID, grpc.NewClient())
resp, err := accSvc.ListAccounts(c.Context, &accounts.ListAccountsRequest{})
if err != nil {
fmt.Println(fmt.Errorf("could not list accounts %w", err))
return err
}
buildAccountsListTable(resp.Accounts).Render()
return nil
}}
}
// buildAccountsListTable creates an ascii table for printing on the cli
func buildAccountsListTable(accs []*accounts.Account) *tw.Table {
table := tw.NewWriter(os.Stdout)
table.SetHeader([]string{"Id", "DisplayName", "Mail", "AccountEnabled"})
table.SetAutoFormatHeaders(false)
for _, acc := range accs {
table.Append([]string{
acc.Id,
acc.DisplayName,
acc.Mail,
strconv.FormatBool(acc.AccountEnabled)})
}
return table
}
+39
View File
@@ -0,0 +1,39 @@
package command
import (
"fmt"
"github.com/micro/cli/v2"
"github.com/micro/go-micro/v2/client/grpc"
"github.com/owncloud/ocis-accounts/pkg/config"
"github.com/owncloud/ocis-accounts/pkg/flagset"
accounts "github.com/owncloud/ocis-accounts/pkg/proto/v0"
"os"
)
// RemoveAccount command deletes an existing account.
func RemoveAccount(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "remove",
Usage: "Removes an existing account",
ArgsUsage: "id",
Aliases: []string{"rm"},
Flags: flagset.RemoveAccountWithConfig(cfg),
Action: func(c *cli.Context) error {
accServiceID := cfg.GRPC.Namespace + "." + cfg.Server.Name
if c.NArg() != 1 {
fmt.Println("Please provide a user-id")
os.Exit(1)
}
uid := c.Args().First()
accSvc := accounts.NewAccountsService(accServiceID, grpc.NewClient())
_, err := accSvc.DeleteAccount(c.Context, &accounts.DeleteAccountRequest{Id: uid})
if err != nil {
fmt.Println(fmt.Errorf("could not delete account %w", err))
return err
}
return nil
}}
}
+117
View File
@@ -0,0 +1,117 @@
package command
import (
"os"
"strings"
"github.com/owncloud/ocis-accounts/pkg/flagset"
"github.com/micro/cli/v2"
"github.com/owncloud/ocis-accounts/pkg/config"
"github.com/owncloud/ocis-accounts/pkg/version"
"github.com/owncloud/ocis-pkg/v2/log"
"github.com/spf13/viper"
)
var (
defaultConfigPaths = []string{"/etc/ocis", "$HOME/.ocis", "./config"}
defaultFilename = "accounts"
)
// Execute is the entry point for the ocis-accounts command.
func Execute() error {
cfg := config.New()
app := &cli.App{
Name: "ocis-accounts",
Version: version.String,
Usage: "Provide accounts and groups for oCIS",
Compiled: version.Compiled(),
Authors: []*cli.Author{
{
Name: "ownCloud GmbH",
Email: "support@owncloud.com",
},
},
Flags: flagset.RootWithConfig(cfg),
Before: func(c *cli.Context) error {
return ParseConfig(c, cfg)
},
Commands: []*cli.Command{
Server(cfg),
AddAccount(cfg),
UpdateAccount(cfg),
ListAccounts(cfg),
InspectAccount(cfg),
RemoveAccount(cfg),
},
}
cli.HelpFlag = &cli.BoolFlag{
Name: "help,h",
Usage: "Show the help",
}
cli.VersionFlag = &cli.BoolFlag{
Name: "version,v",
Usage: "Print the version",
}
return app.Run(os.Args)
}
// NewLogger initializes a service-specific logger instance.
func NewLogger(cfg *config.Config) log.Logger {
return log.NewLogger(
log.Name("accounts"),
log.Level(cfg.Log.Level),
log.Pretty(cfg.Log.Pretty),
log.Color(cfg.Log.Color),
)
}
// ParseConfig loads accounts configuration from Viper known paths.
func ParseConfig(c *cli.Context, cfg *config.Config) error {
logger := NewLogger(cfg)
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.SetEnvPrefix("ACCOUNTS")
viper.AutomaticEnv()
if c.IsSet("config-file") {
viper.SetConfigFile(c.String("config-file"))
} else {
viper.SetConfigName(defaultFilename)
for _, v := range defaultConfigPaths {
viper.AddConfigPath(v)
}
}
if err := viper.ReadInConfig(); err != nil {
switch err.(type) {
case viper.ConfigFileNotFoundError:
logger.Info().
Msg("Continue without config")
case viper.UnsupportedConfigError:
logger.Fatal().
Err(err).
Msg("Unsupported config type")
default:
logger.Fatal().
Err(err).
Msg("Failed to read config")
}
}
if err := viper.Unmarshal(&cfg); err != nil {
logger.Fatal().
Err(err).
Msg("Failed to parse config")
}
return nil
}
+114
View File
@@ -0,0 +1,114 @@
package command
import (
"context"
"os"
"os/signal"
"strings"
"github.com/micro/cli/v2"
"github.com/oklog/run"
"github.com/owncloud/ocis-accounts/pkg/config"
"github.com/owncloud/ocis-accounts/pkg/flagset"
"github.com/owncloud/ocis-accounts/pkg/metrics"
"github.com/owncloud/ocis-accounts/pkg/server/grpc"
"github.com/owncloud/ocis-accounts/pkg/server/http"
svc "github.com/owncloud/ocis-accounts/pkg/service/v0"
)
// Server is the entry point for the server command.
func Server(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "server",
Usage: "Start ocis accounts service",
Description: "uses an LDAP server as the storage backend",
Flags: flagset.ServerWithConfig(cfg),
Before: func(ctx *cli.Context) error {
if cfg.HTTP.Root != "/" {
cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/")
}
// When running on single binary mode the before hook from the root command won't get called. We manually
// call this before hook from ocis command, so the configuration can be loaded.
return ParseConfig(ctx, cfg)
},
Action: func(c *cli.Context) error {
logger := NewLogger(cfg)
var (
gr = run.Group{}
ctx, cancel = context.WithCancel(context.Background())
mtrcs = metrics.New()
)
defer cancel()
handler, err := svc.New(svc.Logger(logger), svc.Config(cfg))
if err != nil {
logger.Fatal().Err(err).Msg("could not initialize service handler")
}
{
server := http.Server(
http.Logger(logger),
http.Name(cfg.Server.Name),
http.Context(ctx),
http.Config(cfg),
http.Metrics(mtrcs),
http.Flags(flagset.RootWithConfig(cfg)),
http.Flags(flagset.ServerWithConfig(cfg)),
http.Handler(handler),
)
gr.Add(server.Run, func(_ error) {
logger.Info().
Str("server", "http").
Msg("Shutting down server")
cancel()
})
}
{
server := grpc.Server(
grpc.Logger(logger),
grpc.Name(cfg.Server.Name),
grpc.Context(ctx),
grpc.Config(cfg),
grpc.Metrics(mtrcs),
grpc.Handler(handler),
)
gr.Add(func() error {
logger.Info().Str("service", server.Name()).Msg("Reporting settings bundles to settings service")
go svc.RegisterSettingsBundles(&logger)
go svc.RegisterPermissions(&logger)
return server.Run()
}, func(_ error) {
logger.Info().
Str("server", "grpc").
Msg("Shutting down server")
cancel()
})
}
{
stop := make(chan os.Signal, 1)
gr.Add(func() error {
signal.Notify(stop, os.Interrupt)
<-stop
return nil
}, func(err error) {
close(stop)
cancel()
})
}
return gr.Run()
},
}
}
+87
View File
@@ -0,0 +1,87 @@
package command
import (
"errors"
"fmt"
"github.com/micro/cli/v2"
"github.com/micro/go-micro/v2/client/grpc"
"github.com/owncloud/ocis-accounts/pkg/config"
"github.com/owncloud/ocis-accounts/pkg/flagset"
accounts "github.com/owncloud/ocis-accounts/pkg/proto/v0"
"google.golang.org/genproto/protobuf/field_mask"
)
// UpdateAccount command for modifying accounts including password policies
func UpdateAccount(cfg *config.Config) *cli.Command {
a := &accounts.Account{
PasswordProfile: &accounts.PasswordProfile{},
}
return &cli.Command{
Name: "update",
Usage: "Make changes to an existing account",
ArgsUsage: "id",
Flags: flagset.UpdateAccountWithConfig(cfg, a),
Before: func(c *cli.Context) error {
if len(c.StringSlice("password_policies")) > 0 {
// StringSliceFlag doesn't support Destination
a.PasswordProfile.PasswordPolicies = c.StringSlice("password_policies")
}
if c.NArg() != 1 {
return errors.New("missing account-id")
}
if c.NumFlags() == 0 {
return errors.New("missing attribute-flags for update")
}
return nil
},
Action: func(c *cli.Context) error {
a.Id = c.Args().First()
accSvcID := cfg.GRPC.Namespace + "." + cfg.Server.Name
accSvc := accounts.NewAccountsService(accSvcID, grpc.NewClient())
_, err := accSvc.UpdateAccount(c.Context, &accounts.UpdateAccountRequest{
Account: a,
UpdateMask: buildAccUpdateMask(c.FlagNames()),
})
if err != nil {
fmt.Println(fmt.Errorf("could not update account %w", err))
return err
}
return nil
}}
}
// buildAccUpdateMask by mapping passed update flags to account fieldNames.
//
// The UpdateMask is passed with the update-request to the server so that
// only the modified values are transferred.
func buildAccUpdateMask(setFlags []string) *field_mask.FieldMask {
var flagToPath = map[string]string{
"enabled": "AccountEnabled",
"displayname": "DisplayName",
"preferred-name": "PreferredName",
"uidnumber": "UidNumber",
"gidnumber": "GidNumber",
"mail": "Mail",
"description": "Description",
"password": "PasswordProfile.Password",
"password-policies": "PasswordProfile.PasswordPolicies",
"force-password-change": "PasswordProfile.ForceChangePasswordNextSignIn",
"force-password-change-mfa": "PasswordProfile.ForceChangePasswordNextSignInWithMfa",
"on-premises-sam-account-name": "OnPremisesSamAccountName",
}
updatedPaths := make([]string, 0)
for _, v := range setFlags {
if _, ok := flagToPath[v]; ok {
updatedPaths = append(updatedPaths, flagToPath[v])
}
}
return &field_mask.FieldMask{Paths: updatedPaths}
}