mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-01-06 04:09:40 -06:00
14
audit/cmd/notifications/main.go
Normal file
14
audit/cmd/notifications/main.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/owncloud/ocis/audit/pkg/command"
|
||||
"github.com/owncloud/ocis/audit/pkg/config"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := command.Execute(config.DefaultConfig()); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
105
audit/pkg/channels/channels.go
Normal file
105
audit/pkg/channels/channels.go
Normal file
@@ -0,0 +1,105 @@
|
||||
// Package channels provides different communication channels to notify users.
|
||||
package channels
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/smtp"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
groups "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/owncloud/ocis/notifications/pkg/config"
|
||||
"github.com/owncloud/ocis/ocis-pkg/log"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Channel defines the methods of a communication channel.
|
||||
type Channel interface {
|
||||
// SendMessage sends a message to users.
|
||||
SendMessage(userIDs []string, msg string) error
|
||||
// SendMessageToGroup sends a message to a group.
|
||||
SendMessageToGroup(groupdID *groups.GroupId, msg string) error
|
||||
}
|
||||
|
||||
// NewMailChannel instantiates a new mail communication channel.
|
||||
func NewMailChannel(cfg config.Config, logger log.Logger) (Channel, error) {
|
||||
gc, err := pool.GetGatewayServiceClient(cfg.Notifications.RevaGateway)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("could not get gateway client")
|
||||
return nil, err
|
||||
}
|
||||
return Mail{
|
||||
gatewayClient: gc,
|
||||
conf: cfg,
|
||||
logger: logger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Mail is the communcation channel for email.
|
||||
type Mail struct {
|
||||
gatewayClient gateway.GatewayAPIClient
|
||||
conf config.Config
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// SendMessage sends a message to all given users.
|
||||
func (m Mail) SendMessage(userIDs []string, msg string) error {
|
||||
to, err := m.getReceiverAddresses(userIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body := []byte(msg)
|
||||
|
||||
smtpConf := m.conf.Notifications.SMTP
|
||||
auth := smtp.PlainAuth("", smtpConf.Sender, smtpConf.Password, smtpConf.Host)
|
||||
if err := smtp.SendMail(smtpConf.Host+":"+smtpConf.Port, auth, smtpConf.Sender, to, body); err != nil {
|
||||
return errors.Wrap(err, "could not send mail")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendMessageToGroup sends a message to all members of the given group.
|
||||
func (m Mail) SendMessageToGroup(groupID *groups.GroupId, msg string) error {
|
||||
// TODO We need an authenticated context here...
|
||||
res, err := m.gatewayClient.GetGroup(context.Background(), &groups.GetGroupRequest{GroupId: groupID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.Status.Code != rpc.Code_CODE_OK {
|
||||
return errors.New("could not get group")
|
||||
}
|
||||
|
||||
members := make([]string, 0, len(res.Group.Members))
|
||||
for _, id := range res.Group.Members {
|
||||
members = append(members, id.OpaqueId)
|
||||
}
|
||||
|
||||
return m.SendMessage(members, msg)
|
||||
}
|
||||
|
||||
func (m Mail) getReceiverAddresses(receivers []string) ([]string, error) {
|
||||
addresses := make([]string, 0, len(receivers))
|
||||
for _, id := range receivers {
|
||||
// Authenticate is too costly but at the moment our only option to get the user.
|
||||
// We don't have an authenticated context so calling `GetUser` doesn't work.
|
||||
res, err := m.gatewayClient.Authenticate(context.Background(), &gateway.AuthenticateRequest{
|
||||
Type: "machine",
|
||||
ClientId: "userid:" + id,
|
||||
ClientSecret: m.conf.Notifications.MachineAuthSecret,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.Status.Code != rpc.Code_CODE_OK {
|
||||
m.logger.Error().
|
||||
Interface("status", res.Status).
|
||||
Str("receiver_id", id).
|
||||
Msg("could not get user")
|
||||
continue
|
||||
}
|
||||
addresses = append(addresses, res.User.Mail)
|
||||
}
|
||||
|
||||
return addresses, nil
|
||||
}
|
||||
18
audit/pkg/command/health.go
Normal file
18
audit/pkg/command/health.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"github.com/owncloud/ocis/notifications/pkg/config"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// Health is the entrypoint for the health command.
|
||||
func Health(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "health",
|
||||
Usage: "Check health status",
|
||||
Action: func(c *cli.Context) error {
|
||||
// Not implemented
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
64
audit/pkg/command/root.go
Normal file
64
audit/pkg/command/root.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/owncloud/ocis/notifications/pkg/config"
|
||||
"github.com/owncloud/ocis/ocis-pkg/clihelper"
|
||||
ociscfg "github.com/owncloud/ocis/ocis-pkg/config"
|
||||
"github.com/thejerf/suture/v4"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// GetCommands provides all commands for this service
|
||||
func GetCommands(cfg *config.Config) cli.Commands {
|
||||
return []*cli.Command{
|
||||
// start this service
|
||||
Server(cfg),
|
||||
|
||||
// interaction with this service
|
||||
|
||||
// infos about this service
|
||||
Health(cfg),
|
||||
Version(cfg),
|
||||
}
|
||||
}
|
||||
|
||||
// Execute is the entry point for the notifications command.
|
||||
func Execute(cfg *config.Config) error {
|
||||
app := clihelper.DefaultApp(&cli.App{
|
||||
Name: "notifications",
|
||||
Usage: "starts notifications service",
|
||||
Commands: GetCommands(cfg),
|
||||
})
|
||||
|
||||
cli.HelpFlag = &cli.BoolFlag{
|
||||
Name: "help,h",
|
||||
Usage: "Show the help",
|
||||
}
|
||||
|
||||
return app.Run(os.Args)
|
||||
}
|
||||
|
||||
// SutureService allows for the notifications command to be embedded and supervised by a suture supervisor tree.
|
||||
type SutureService struct {
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
// NewSutureService creates a new notifications.SutureService
|
||||
func NewSutureService(cfg *ociscfg.Config) suture.Service {
|
||||
cfg.Settings.Commons = cfg.Commons
|
||||
return SutureService{
|
||||
cfg: cfg.Notifications,
|
||||
}
|
||||
}
|
||||
|
||||
func (s SutureService) Serve(ctx context.Context) error {
|
||||
s.cfg.Context = ctx
|
||||
if err := Execute(s.cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
50
audit/pkg/command/server.go
Normal file
50
audit/pkg/command/server.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/asim/go-micro/plugins/events/nats/v4"
|
||||
"github.com/cs3org/reva/v2/pkg/events"
|
||||
"github.com/cs3org/reva/v2/pkg/events/server"
|
||||
"github.com/owncloud/ocis/notifications/pkg/channels"
|
||||
"github.com/owncloud/ocis/notifications/pkg/config"
|
||||
"github.com/owncloud/ocis/notifications/pkg/config/parser"
|
||||
"github.com/owncloud/ocis/notifications/pkg/logging"
|
||||
"github.com/owncloud/ocis/notifications/pkg/service"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// Server is the entrypoint for the server command.
|
||||
func Server(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "server",
|
||||
Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name),
|
||||
Category: "server",
|
||||
Before: func(c *cli.Context) error {
|
||||
return parser.ParseConfig(cfg)
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
logger := logging.Configure(cfg.Service.Name, cfg.Log)
|
||||
|
||||
evs := []events.Unmarshaller{
|
||||
events.ShareCreated{},
|
||||
}
|
||||
|
||||
evtsCfg := cfg.Notifications.Events
|
||||
client, err := server.NewNatsStream(nats.Address(evtsCfg.Endpoint), nats.ClusterID(evtsCfg.Cluster))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
evts, err := events.Consume(client, evtsCfg.ConsumerGroup, evs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
channel, err := channels.NewMailChannel(*cfg, logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
svc := service.NewEventsNotifier(evts, channel, logger)
|
||||
return svc.Run()
|
||||
},
|
||||
}
|
||||
}
|
||||
19
audit/pkg/command/version.go
Normal file
19
audit/pkg/command/version.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"github.com/owncloud/ocis/notifications/pkg/config"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// Version prints the service versions of all running instances.
|
||||
func Version(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "version",
|
||||
Usage: "print the version of this binary and the running extension instances",
|
||||
Category: "info",
|
||||
Action: func(c *cli.Context) error {
|
||||
// not implemented
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
44
audit/pkg/config/config.go
Normal file
44
audit/pkg/config/config.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/owncloud/ocis/ocis-pkg/shared"
|
||||
)
|
||||
|
||||
// Config combines all available configuration parts.
|
||||
type Config struct {
|
||||
*shared.Commons
|
||||
|
||||
Service Service
|
||||
|
||||
Log *Log `ocisConfig:"log"`
|
||||
Debug Debug `ocisConfig:"debug"`
|
||||
|
||||
Notifications Notifications `ocisConfig:"notifications"`
|
||||
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
// Notifications definces the config options for the notifications service.
|
||||
type Notifications struct {
|
||||
SMTP SMTP `ocisConfig:"SMTP"`
|
||||
Events Events `ocisConfig:"events"`
|
||||
RevaGateway string `ocisConfig:"reva_gateway" env:"REVA_GATEWAY;NOTIFICATIONS_REVA_GATEWAY"`
|
||||
MachineAuthSecret string `ocisConfig:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY;NOTIFICATIONS_MACHINE_AUTH_API_KEY"`
|
||||
}
|
||||
|
||||
// SMTP combines the smtp configuration options.
|
||||
type SMTP struct {
|
||||
Host string `ocisConfig:"smtp_host" env:"NOTIFICATIONS_SMTP_HOST"`
|
||||
Port string `ocisConfig:"smtp_port" env:"NOTIFICATIONS_SMTP_PORT"`
|
||||
Sender string `ocisConfig:"smtp_sender" env:"NOTIFICATIONS_SMTP_SENDER"`
|
||||
Password string `ocisConfig:"smtp_password" env:"NOTIFICATIONS_SMTP_PASSWORD"`
|
||||
}
|
||||
|
||||
// Events combines the configuration options for the event bus.
|
||||
type Events struct {
|
||||
Endpoint string `ocisConfig:"events_endpoint" env:"NOTIFICATIONS_EVENTS_ENDPOINT"`
|
||||
Cluster string `ocisConfig:"events_cluster" env:"NOTIFICATIONS_EVENTS_CLUSTER"`
|
||||
ConsumerGroup string `ocisConfig:"events_group" env:"NOTIFICATIONS_EVENTS_GROUP"`
|
||||
}
|
||||
9
audit/pkg/config/debug.go
Normal file
9
audit/pkg/config/debug.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package config
|
||||
|
||||
// Debug defines the available debug configuration.
|
||||
type Debug struct {
|
||||
Addr string `ocisConfig:"addr" env:"NOTIFICATIONS_DEBUG_ADDR"`
|
||||
Token string `ocisConfig:"token" env:"NOTIFICATIONS_DEBUG_TOKEN"`
|
||||
Pprof bool `ocisConfig:"pprof" env:"NOTIFICATIONS_DEBUG_PPROF"`
|
||||
Zpages bool `ocisConfig:"zpages" env:"NOTIFICATIONS_DEBUG_ZPAGES"`
|
||||
}
|
||||
27
audit/pkg/config/defaultconfig.go
Normal file
27
audit/pkg/config/defaultconfig.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package config
|
||||
|
||||
// NOTE: Most of this configuration is not needed to keep it as simple as possible
|
||||
// TODO: Clean up unneeded configuration
|
||||
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
Service: Service{
|
||||
Name: "notifications",
|
||||
},
|
||||
Notifications: Notifications{
|
||||
SMTP: SMTP{
|
||||
Host: "127.0.0.1",
|
||||
Port: "1025",
|
||||
Sender: "god@example.com",
|
||||
Password: "godisdead",
|
||||
},
|
||||
Events: Events{
|
||||
Endpoint: "127.0.0.1:9233",
|
||||
Cluster: "test-cluster",
|
||||
ConsumerGroup: "notifications",
|
||||
},
|
||||
RevaGateway: "127.0.0.1:9142",
|
||||
MachineAuthSecret: "change-me-please",
|
||||
},
|
||||
}
|
||||
}
|
||||
9
audit/pkg/config/log.go
Normal file
9
audit/pkg/config/log.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package config
|
||||
|
||||
// Log defines the available log configuration.
|
||||
type Log struct {
|
||||
Level string `mapstructure:"level" env:"OCIS_LOG_LEVEL;NOTIFICATIONS_LOG_LEVEL"`
|
||||
Pretty bool `mapstructure:"pretty" env:"OCIS_LOG_PRETTY;NOTIFICATIONS_LOG_PRETTY"`
|
||||
Color bool `mapstructure:"color" env:"OCIS_LOG_COLOR;NOTIFICATIONS_LOG_COLOR"`
|
||||
File string `mapstructure:"file" env:"OCIS_LOG_FILE;NOTIFICATIONS_LOG_FILE"`
|
||||
}
|
||||
40
audit/pkg/config/parser/parse.go
Normal file
40
audit/pkg/config/parser/parse.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/owncloud/ocis/notifications/pkg/config"
|
||||
ociscfg "github.com/owncloud/ocis/ocis-pkg/config"
|
||||
|
||||
"github.com/owncloud/ocis/ocis-pkg/config/envdecode"
|
||||
)
|
||||
|
||||
// ParseConfig loads accounts configuration from known paths.
|
||||
func ParseConfig(cfg *config.Config) error {
|
||||
_, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// provide with defaults for shared logging, since we need a valid destination address for BindEnv.
|
||||
if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil {
|
||||
cfg.Log = &config.Log{
|
||||
Level: cfg.Commons.Log.Level,
|
||||
Pretty: cfg.Commons.Log.Pretty,
|
||||
Color: cfg.Commons.Log.Color,
|
||||
File: cfg.Commons.Log.File,
|
||||
}
|
||||
} else if cfg.Log == nil {
|
||||
cfg.Log = &config.Log{}
|
||||
}
|
||||
|
||||
// load all env variables relevant to the config in the current context.
|
||||
if err := envdecode.Decode(cfg); err != nil {
|
||||
// no environment variable set for this config is an expected "error"
|
||||
if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
6
audit/pkg/config/service.go
Normal file
6
audit/pkg/config/service.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package config
|
||||
|
||||
// Service defines the available service configuration.
|
||||
type Service struct {
|
||||
Name string
|
||||
}
|
||||
17
audit/pkg/logging/logging.go
Normal file
17
audit/pkg/logging/logging.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"github.com/owncloud/ocis/notifications/pkg/config"
|
||||
"github.com/owncloud/ocis/ocis-pkg/log"
|
||||
)
|
||||
|
||||
// LoggerFromConfig initializes a service-specific logger instance.
|
||||
func Configure(name string, cfg *config.Log) log.Logger {
|
||||
return log.NewLogger(
|
||||
log.Name(name),
|
||||
log.Level(cfg.Level),
|
||||
log.Pretty(cfg.Pretty),
|
||||
log.Color(cfg.Color),
|
||||
log.File(cfg.File),
|
||||
)
|
||||
}
|
||||
79
audit/pkg/service/events.go
Normal file
79
audit/pkg/service/events.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/asim/go-micro/plugins/events/nats/v4"
|
||||
"github.com/cs3org/reva/v2/pkg/events"
|
||||
"github.com/cs3org/reva/v2/pkg/events/server"
|
||||
"github.com/owncloud/ocis/audit/pkg/config"
|
||||
"github.com/owncloud/ocis/audit/pkg/types"
|
||||
"github.com/owncloud/ocis/ocis-pkg/log"
|
||||
)
|
||||
|
||||
func startConsumer(c config.Eventstream, log log.Logger) (<-chan interface{}, error) {
|
||||
s, err := server.NewNatsStream(nats.Address(c.Address), nats.ClusterID(c.ClusterID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return events.Consume(s, "audit", events.ShareCreated{})
|
||||
}
|
||||
|
||||
func startAuditLogger(c config.Auditlog, ch <-chan interface{}, log log.Logger) {
|
||||
for {
|
||||
i := <-ch
|
||||
|
||||
var auditEvent interface{}
|
||||
switch ev := i.(type) {
|
||||
case events.ShareCreated:
|
||||
auditEvent = types.ShareCreated(ev)
|
||||
default:
|
||||
log.Error().Interface("event", ev).Msg(fmt.Sprintf("can't handle event of type '%T'", ev))
|
||||
continue
|
||||
|
||||
}
|
||||
|
||||
b, err := marshal(auditEvent, c.Format)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error marshaling the event")
|
||||
continue
|
||||
}
|
||||
|
||||
if c.LogToConsole {
|
||||
log.Info().Msg(string(b))
|
||||
}
|
||||
|
||||
if c.LogToFile {
|
||||
err := writeToFile(c.FilePath, b)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error writing audit log file")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func writeToFile(path string, ev []byte) error {
|
||||
file, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
if _, err := fmt.Fprintln(file, string(ev)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func marshal(ev interface{}, format string) ([]byte, error) {
|
||||
switch format {
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported format '%s'", format)
|
||||
case "json":
|
||||
return json.Marshal(ev)
|
||||
}
|
||||
}
|
||||
21
audit/pkg/service/instrument.go
Normal file
21
audit/pkg/service/instrument.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package svc
|
||||
|
||||
/*
|
||||
// NewInstrument returns a service that instruments metrics.
|
||||
func NewInstrument(next Service, metrics *metrics.Metrics) Service {
|
||||
return instrument{
|
||||
next: next,
|
||||
metrics: metrics,
|
||||
}
|
||||
}
|
||||
|
||||
type instrument struct {
|
||||
next Service
|
||||
metrics *metrics.Metrics
|
||||
}
|
||||
|
||||
// ListenForEvents implements service interface
|
||||
func (i instrument) ListenForEvents() {
|
||||
i.next.ListenForEvents()
|
||||
}
|
||||
*/
|
||||
23
audit/pkg/service/logging.go
Normal file
23
audit/pkg/service/logging.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"github.com/owncloud/ocis/ocis-pkg/log"
|
||||
)
|
||||
|
||||
// NewLogging returns a service that logs messages.
|
||||
func NewLogging(next Service, logger log.Logger) Service {
|
||||
return logging{
|
||||
next: next,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
type logging struct {
|
||||
next Service
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// ListenForEvents implements service interface
|
||||
func (l logging) ListenForEvents() {
|
||||
l.next.ListenForEvents()
|
||||
}
|
||||
50
audit/pkg/service/option.go
Normal file
50
audit/pkg/service/option.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/owncloud/ocis/audit/pkg/config"
|
||||
"github.com/owncloud/ocis/ocis-pkg/log"
|
||||
)
|
||||
|
||||
// Option defines a single option function.
|
||||
type Option func(o *Options)
|
||||
|
||||
// Options defines the available options for this package.
|
||||
type Options struct {
|
||||
Logger log.Logger
|
||||
Config *config.Config
|
||||
Middleware []func(http.Handler) http.Handler
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
func newOptions(opts ...Option) Options {
|
||||
opt := Options{}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&opt)
|
||||
}
|
||||
|
||||
return opt
|
||||
}
|
||||
|
||||
// Logger provides a function to set the logger option.
|
||||
func Logger(val log.Logger) Option {
|
||||
return func(o *Options) {
|
||||
o.Logger = val
|
||||
}
|
||||
}
|
||||
|
||||
// Config provides a function to set the config option.
|
||||
func Config(val *config.Config) Option {
|
||||
return func(o *Options) {
|
||||
o.Config = val
|
||||
}
|
||||
}
|
||||
|
||||
// Middleware provides a function to set the middleware option.
|
||||
func Middleware(val ...func(http.Handler) http.Handler) Option {
|
||||
return func(o *Options) {
|
||||
o.Middleware = val
|
||||
}
|
||||
}
|
||||
48
audit/pkg/service/service.go
Normal file
48
audit/pkg/service/service.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/owncloud/ocis/audit/pkg/config"
|
||||
"github.com/owncloud/ocis/ocis-pkg/log"
|
||||
)
|
||||
|
||||
// Service defines the extension handlers.
|
||||
type Service interface {
|
||||
ListenForEvents()
|
||||
}
|
||||
|
||||
// NewService returns a service implementation for Service.
|
||||
func NewService(opts ...Option) Service {
|
||||
options := newOptions(opts...)
|
||||
|
||||
m := chi.NewMux()
|
||||
m.Use(options.Middleware...)
|
||||
|
||||
svc := Audit{
|
||||
logger: options.Logger,
|
||||
config: options.Config,
|
||||
mux: m,
|
||||
}
|
||||
|
||||
go svc.ListenForEvents()
|
||||
return svc
|
||||
}
|
||||
|
||||
// Audit defines implements the business logic for Service.
|
||||
type Audit struct {
|
||||
logger log.Logger
|
||||
config *config.Config
|
||||
mux *chi.Mux
|
||||
}
|
||||
|
||||
// ListenForEvents hooks into event queue and logs interesting events
|
||||
func (g Audit) ListenForEvents() {
|
||||
log := g.logger
|
||||
ch, err := startConsumer(g.config.Eventstream, log)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("can't listen for events")
|
||||
return
|
||||
}
|
||||
|
||||
startAuditLogger(g.config.Auditlog, ch, log)
|
||||
}
|
||||
17
audit/pkg/service/tracing.go
Normal file
17
audit/pkg/service/tracing.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package svc
|
||||
|
||||
// NewTracing returns a service that instruments traces.
|
||||
func NewTracing(next Service) Service {
|
||||
return tracing{
|
||||
next: next,
|
||||
}
|
||||
}
|
||||
|
||||
type tracing struct {
|
||||
next Service
|
||||
}
|
||||
|
||||
// ListenForEvents implements service interface
|
||||
func (t tracing) ListenForEvents() {
|
||||
t.next.ListenForEvents()
|
||||
}
|
||||
79
audit/pkg/types/conversion.go
Normal file
79
audit/pkg/types/conversion.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/cs3org/reva/v2/pkg/events"
|
||||
)
|
||||
|
||||
// actions
|
||||
const (
|
||||
actionShareCreated = "file_shared"
|
||||
)
|
||||
|
||||
// messages
|
||||
const (
|
||||
messageShareCreated = "user '%s' shared file '%s' with '%s'"
|
||||
)
|
||||
|
||||
// BasicAuditEvent creates an AuditEvent from given values
|
||||
func BasicAuditEvent(uid string, ctime string, msg string, action string) AuditEvent {
|
||||
return AuditEvent{
|
||||
User: uid,
|
||||
Time: ctime,
|
||||
App: "admin_audit",
|
||||
Message: msg,
|
||||
Action: action,
|
||||
Level: 1,
|
||||
|
||||
// NOTE: those values are not in the events and can therefore not be filled at the moment
|
||||
RemoteAddr: "",
|
||||
URL: "",
|
||||
Method: "",
|
||||
UserAgent: "",
|
||||
CLI: false,
|
||||
}
|
||||
}
|
||||
|
||||
// SharingAuditEvent creates an AuditEventSharing from given values
|
||||
func SharingAuditEvent(fileid string, uid string, base AuditEvent) AuditEventSharing {
|
||||
return AuditEventSharing{
|
||||
AuditEvent: base,
|
||||
FileID: fileid,
|
||||
Owner: uid,
|
||||
|
||||
// NOTE: those values are not in the events and can therefore not be filled at the moment
|
||||
ShareID: "",
|
||||
Path: "",
|
||||
}
|
||||
}
|
||||
|
||||
// ShareCreated converts a ShareCreated Event to an AuditEventShareCreated
|
||||
func ShareCreated(ev events.ShareCreated) AuditEventShareCreated {
|
||||
with := ""
|
||||
typ := ""
|
||||
if ev.GranteeUserID != nil && ev.GranteeUserID.OpaqueId != "" {
|
||||
with = ev.GranteeUserID.OpaqueId
|
||||
typ = "user"
|
||||
} else if ev.GranteeGroupID != nil && ev.GranteeGroupID.OpaqueId != "" {
|
||||
with = ev.GranteeGroupID.OpaqueId
|
||||
typ = "group"
|
||||
}
|
||||
uid := ev.Sharer.OpaqueId
|
||||
t := time.Unix(int64(ev.CTime.Seconds), int64(ev.CTime.Nanos)).Format(time.RFC3339)
|
||||
base := BasicAuditEvent(uid, t, fmt.Sprintf(messageShareCreated, uid, ev.ItemID.OpaqueId, with), actionShareCreated)
|
||||
return AuditEventShareCreated{
|
||||
AuditEventSharing: SharingAuditEvent(ev.ItemID.OpaqueId, uid, base),
|
||||
ShareOwner: uid,
|
||||
ShareWith: with,
|
||||
ShareType: typ,
|
||||
|
||||
// NOTE: those values are not in the events and can therefore not be filled at the moment
|
||||
ItemType: "",
|
||||
ExpirationDate: "",
|
||||
SharePass: false,
|
||||
Permissions: "",
|
||||
ShareToken: "",
|
||||
}
|
||||
}
|
||||
40
audit/pkg/types/types.go
Normal file
40
audit/pkg/types/types.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package types
|
||||
|
||||
// AuditEvent is the basic audit event
|
||||
type AuditEvent struct {
|
||||
RemoteAddr string // the remote client IP
|
||||
User string // the UID of the user performing the action. Or "IP x.x.x.x.", "cron", "CLI", "unknown"
|
||||
URL string // the process request URI
|
||||
Method string // the HTTP request method
|
||||
UserAgent string // the HTTP request user agent
|
||||
Time string // the time of the event eg: 2018-05-08T08:26:00+00:00
|
||||
App string // always 'admin_audit'
|
||||
Message string // sentence explaining the action
|
||||
Action string // unique action identifier eg: file_delete or public_link_created
|
||||
CLI bool // if the action was performed from the CLI
|
||||
Level int // the log level of the entry (usually 1 for audit events)
|
||||
}
|
||||
|
||||
// AuditEventSharing is the basic audit event for shares
|
||||
type AuditEventSharing struct {
|
||||
AuditEvent
|
||||
|
||||
FileID string // The file identifier for the item shared.
|
||||
Owner string // The UID of the owner of the shared item.
|
||||
Path string // The path to the shared item.
|
||||
ShareID string // The sharing identifier. (not available for public_link_accessed or when recipient unshares)
|
||||
}
|
||||
|
||||
// AuditEventShareCreated is the event logged when a share is created
|
||||
type AuditEventShareCreated struct {
|
||||
AuditEventSharing
|
||||
|
||||
ItemType string // file or folder
|
||||
ExpirationDate string // The text expiration date in format 'yyyy-mm-dd'
|
||||
SharePass bool // If the share is password protected.
|
||||
Permissions string // The permissions string eg: "READ"
|
||||
ShareType string // group user or link
|
||||
ShareWith string // The UID or GID of the share recipient. (not available for public link)
|
||||
ShareOwner string // The UID of the share owner.
|
||||
ShareToken string // For link shares the unique token, else null
|
||||
}
|
||||
3
go.mod
3
go.mod
@@ -25,6 +25,7 @@ require (
|
||||
github.com/cs3org/reva/v2 v2.0.0-20220304131900-b8be80d1ba81
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/glauth/glauth/v2 v2.0.0-20211021011345-ef3151c28733
|
||||
github.com/go-chi/chi v4.0.2+incompatible
|
||||
github.com/go-chi/chi/v5 v5.0.7
|
||||
github.com/go-chi/cors v1.2.0
|
||||
github.com/go-chi/render v1.0.1
|
||||
@@ -46,6 +47,7 @@ require (
|
||||
github.com/mennanov/fieldmask-utils v0.5.0
|
||||
github.com/mitchellh/mapstructure v1.4.3
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
||||
github.com/nats-io/nats-server/v2 v2.7.3
|
||||
github.com/nats-io/nats-streaming-server v0.24.2
|
||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
||||
github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba
|
||||
@@ -208,7 +210,6 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mschoch/smat v0.2.0 // indirect
|
||||
github.com/nats-io/jwt/v2 v2.2.1-0.20220113022732-58e87895b296 // indirect
|
||||
github.com/nats-io/nats-server/v2 v2.7.3 // indirect
|
||||
github.com/nats-io/nats.go v1.13.1-0.20220121202836-972a071d373d // indirect
|
||||
github.com/nats-io/nkeys v0.3.0 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
|
||||
Reference in New Issue
Block a user