gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

notification.go (13698B)


      1 /*
      2  * MinIO Go Library for Amazon S3 Compatible Cloud Storage
      3  * Copyright 2020 MinIO, Inc.
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package notification
     19 
     20 import (
     21 	"encoding/xml"
     22 	"errors"
     23 	"fmt"
     24 	"strings"
     25 
     26 	"github.com/minio/minio-go/v7/pkg/set"
     27 )
     28 
     29 // EventType is a S3 notification event associated to the bucket notification configuration
     30 type EventType string
     31 
     32 // The role of all event types are described in :
     33 //
     34 //	http://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html#notification-how-to-event-types-and-destinations
     35 const (
     36 	ObjectCreatedAll                     EventType = "s3:ObjectCreated:*"
     37 	ObjectCreatedPut                     EventType = "s3:ObjectCreated:Put"
     38 	ObjectCreatedPost                    EventType = "s3:ObjectCreated:Post"
     39 	ObjectCreatedCopy                    EventType = "s3:ObjectCreated:Copy"
     40 	ObjectCreatedCompleteMultipartUpload EventType = "s3:ObjectCreated:CompleteMultipartUpload"
     41 	ObjectAccessedGet                    EventType = "s3:ObjectAccessed:Get"
     42 	ObjectAccessedHead                   EventType = "s3:ObjectAccessed:Head"
     43 	ObjectAccessedAll                    EventType = "s3:ObjectAccessed:*"
     44 	ObjectRemovedAll                     EventType = "s3:ObjectRemoved:*"
     45 	ObjectRemovedDelete                  EventType = "s3:ObjectRemoved:Delete"
     46 	ObjectRemovedDeleteMarkerCreated     EventType = "s3:ObjectRemoved:DeleteMarkerCreated"
     47 	ObjectReducedRedundancyLostObject    EventType = "s3:ReducedRedundancyLostObject"
     48 	BucketCreatedAll                     EventType = "s3:BucketCreated:*"
     49 	BucketRemovedAll                     EventType = "s3:BucketRemoved:*"
     50 )
     51 
     52 // FilterRule - child of S3Key, a tag in the notification xml which
     53 // carries suffix/prefix filters
     54 type FilterRule struct {
     55 	Name  string `xml:"Name"`
     56 	Value string `xml:"Value"`
     57 }
     58 
     59 // S3Key - child of Filter, a tag in the notification xml which
     60 // carries suffix/prefix filters
     61 type S3Key struct {
     62 	FilterRules []FilterRule `xml:"FilterRule,omitempty"`
     63 }
     64 
     65 // Filter - a tag in the notification xml structure which carries
     66 // suffix/prefix filters
     67 type Filter struct {
     68 	S3Key S3Key `xml:"S3Key,omitempty"`
     69 }
     70 
     71 // Arn - holds ARN information that will be sent to the web service,
     72 // ARN desciption can be found in http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html
     73 type Arn struct {
     74 	Partition string
     75 	Service   string
     76 	Region    string
     77 	AccountID string
     78 	Resource  string
     79 }
     80 
     81 // NewArn creates new ARN based on the given partition, service, region, account id and resource
     82 func NewArn(partition, service, region, accountID, resource string) Arn {
     83 	return Arn{
     84 		Partition: partition,
     85 		Service:   service,
     86 		Region:    region,
     87 		AccountID: accountID,
     88 		Resource:  resource,
     89 	}
     90 }
     91 
     92 var (
     93 	// ErrInvalidArnPrefix is returned when ARN string format does not start with 'arn'
     94 	ErrInvalidArnPrefix = errors.New("invalid ARN format, must start with 'arn:'")
     95 	// ErrInvalidArnFormat is returned when ARN string format is not valid
     96 	ErrInvalidArnFormat = errors.New("invalid ARN format, must be 'arn:<partition>:<service>:<region>:<accountID>:<resource>'")
     97 )
     98 
     99 // NewArnFromString parses string representation of ARN into Arn object.
    100 // Returns an error if the string format is incorrect.
    101 func NewArnFromString(arn string) (Arn, error) {
    102 	parts := strings.Split(arn, ":")
    103 	if len(parts) != 6 {
    104 		return Arn{}, ErrInvalidArnFormat
    105 	}
    106 	if parts[0] != "arn" {
    107 		return Arn{}, ErrInvalidArnPrefix
    108 	}
    109 
    110 	return NewArn(parts[1], parts[2], parts[3], parts[4], parts[5]), nil
    111 }
    112 
    113 // String returns the string format of the ARN
    114 func (arn Arn) String() string {
    115 	return "arn:" + arn.Partition + ":" + arn.Service + ":" + arn.Region + ":" + arn.AccountID + ":" + arn.Resource
    116 }
    117 
    118 // Config - represents one single notification configuration
    119 // such as topic, queue or lambda configuration.
    120 type Config struct {
    121 	ID     string      `xml:"Id,omitempty"`
    122 	Arn    Arn         `xml:"-"`
    123 	Events []EventType `xml:"Event"`
    124 	Filter *Filter     `xml:"Filter,omitempty"`
    125 }
    126 
    127 // NewConfig creates one notification config and sets the given ARN
    128 func NewConfig(arn Arn) Config {
    129 	return Config{Arn: arn, Filter: &Filter{}}
    130 }
    131 
    132 // AddEvents adds one event to the current notification config
    133 func (t *Config) AddEvents(events ...EventType) {
    134 	t.Events = append(t.Events, events...)
    135 }
    136 
    137 // AddFilterSuffix sets the suffix configuration to the current notification config
    138 func (t *Config) AddFilterSuffix(suffix string) {
    139 	if t.Filter == nil {
    140 		t.Filter = &Filter{}
    141 	}
    142 	newFilterRule := FilterRule{Name: "suffix", Value: suffix}
    143 	// Replace any suffix rule if existing and add to the list otherwise
    144 	for index := range t.Filter.S3Key.FilterRules {
    145 		if t.Filter.S3Key.FilterRules[index].Name == "suffix" {
    146 			t.Filter.S3Key.FilterRules[index] = newFilterRule
    147 			return
    148 		}
    149 	}
    150 	t.Filter.S3Key.FilterRules = append(t.Filter.S3Key.FilterRules, newFilterRule)
    151 }
    152 
    153 // AddFilterPrefix sets the prefix configuration to the current notification config
    154 func (t *Config) AddFilterPrefix(prefix string) {
    155 	if t.Filter == nil {
    156 		t.Filter = &Filter{}
    157 	}
    158 	newFilterRule := FilterRule{Name: "prefix", Value: prefix}
    159 	// Replace any prefix rule if existing and add to the list otherwise
    160 	for index := range t.Filter.S3Key.FilterRules {
    161 		if t.Filter.S3Key.FilterRules[index].Name == "prefix" {
    162 			t.Filter.S3Key.FilterRules[index] = newFilterRule
    163 			return
    164 		}
    165 	}
    166 	t.Filter.S3Key.FilterRules = append(t.Filter.S3Key.FilterRules, newFilterRule)
    167 }
    168 
    169 // EqualEventTypeList tells whether a and b contain the same events
    170 func EqualEventTypeList(a, b []EventType) bool {
    171 	if len(a) != len(b) {
    172 		return false
    173 	}
    174 	setA := set.NewStringSet()
    175 	for _, i := range a {
    176 		setA.Add(string(i))
    177 	}
    178 
    179 	setB := set.NewStringSet()
    180 	for _, i := range b {
    181 		setB.Add(string(i))
    182 	}
    183 
    184 	return setA.Difference(setB).IsEmpty()
    185 }
    186 
    187 // EqualFilterRuleList tells whether a and b contain the same filters
    188 func EqualFilterRuleList(a, b []FilterRule) bool {
    189 	if len(a) != len(b) {
    190 		return false
    191 	}
    192 
    193 	setA := set.NewStringSet()
    194 	for _, i := range a {
    195 		setA.Add(fmt.Sprintf("%s-%s", i.Name, i.Value))
    196 	}
    197 
    198 	setB := set.NewStringSet()
    199 	for _, i := range b {
    200 		setB.Add(fmt.Sprintf("%s-%s", i.Name, i.Value))
    201 	}
    202 
    203 	return setA.Difference(setB).IsEmpty()
    204 }
    205 
    206 // Equal returns whether this `Config` is equal to another defined by the passed parameters
    207 func (t *Config) Equal(events []EventType, prefix, suffix string) bool {
    208 	if t == nil {
    209 		return false
    210 	}
    211 
    212 	// Compare events
    213 	passEvents := EqualEventTypeList(t.Events, events)
    214 
    215 	// Compare filters
    216 	var newFilterRules []FilterRule
    217 	if prefix != "" {
    218 		newFilterRules = append(newFilterRules, FilterRule{Name: "prefix", Value: prefix})
    219 	}
    220 	if suffix != "" {
    221 		newFilterRules = append(newFilterRules, FilterRule{Name: "suffix", Value: suffix})
    222 	}
    223 
    224 	var currentFilterRules []FilterRule
    225 	if t.Filter != nil {
    226 		currentFilterRules = t.Filter.S3Key.FilterRules
    227 	}
    228 
    229 	passFilters := EqualFilterRuleList(currentFilterRules, newFilterRules)
    230 	return passEvents && passFilters
    231 }
    232 
    233 // TopicConfig carries one single topic notification configuration
    234 type TopicConfig struct {
    235 	Config
    236 	Topic string `xml:"Topic"`
    237 }
    238 
    239 // QueueConfig carries one single queue notification configuration
    240 type QueueConfig struct {
    241 	Config
    242 	Queue string `xml:"Queue"`
    243 }
    244 
    245 // LambdaConfig carries one single cloudfunction notification configuration
    246 type LambdaConfig struct {
    247 	Config
    248 	Lambda string `xml:"CloudFunction"`
    249 }
    250 
    251 // Configuration - the struct that represents the whole XML to be sent to the web service
    252 type Configuration struct {
    253 	XMLName       xml.Name       `xml:"NotificationConfiguration"`
    254 	LambdaConfigs []LambdaConfig `xml:"CloudFunctionConfiguration"`
    255 	TopicConfigs  []TopicConfig  `xml:"TopicConfiguration"`
    256 	QueueConfigs  []QueueConfig  `xml:"QueueConfiguration"`
    257 }
    258 
    259 // AddTopic adds a given topic config to the general bucket notification config
    260 func (b *Configuration) AddTopic(topicConfig Config) bool {
    261 	newTopicConfig := TopicConfig{Config: topicConfig, Topic: topicConfig.Arn.String()}
    262 	for _, n := range b.TopicConfigs {
    263 		// If new config matches existing one
    264 		if n.Topic == newTopicConfig.Arn.String() && newTopicConfig.Filter == n.Filter {
    265 
    266 			existingConfig := set.NewStringSet()
    267 			for _, v := range n.Events {
    268 				existingConfig.Add(string(v))
    269 			}
    270 
    271 			newConfig := set.NewStringSet()
    272 			for _, v := range topicConfig.Events {
    273 				newConfig.Add(string(v))
    274 			}
    275 
    276 			if !newConfig.Intersection(existingConfig).IsEmpty() {
    277 				return false
    278 			}
    279 		}
    280 	}
    281 	b.TopicConfigs = append(b.TopicConfigs, newTopicConfig)
    282 	return true
    283 }
    284 
    285 // AddQueue adds a given queue config to the general bucket notification config
    286 func (b *Configuration) AddQueue(queueConfig Config) bool {
    287 	newQueueConfig := QueueConfig{Config: queueConfig, Queue: queueConfig.Arn.String()}
    288 	for _, n := range b.QueueConfigs {
    289 		if n.Queue == newQueueConfig.Arn.String() && newQueueConfig.Filter == n.Filter {
    290 
    291 			existingConfig := set.NewStringSet()
    292 			for _, v := range n.Events {
    293 				existingConfig.Add(string(v))
    294 			}
    295 
    296 			newConfig := set.NewStringSet()
    297 			for _, v := range queueConfig.Events {
    298 				newConfig.Add(string(v))
    299 			}
    300 
    301 			if !newConfig.Intersection(existingConfig).IsEmpty() {
    302 				return false
    303 			}
    304 		}
    305 	}
    306 	b.QueueConfigs = append(b.QueueConfigs, newQueueConfig)
    307 	return true
    308 }
    309 
    310 // AddLambda adds a given lambda config to the general bucket notification config
    311 func (b *Configuration) AddLambda(lambdaConfig Config) bool {
    312 	newLambdaConfig := LambdaConfig{Config: lambdaConfig, Lambda: lambdaConfig.Arn.String()}
    313 	for _, n := range b.LambdaConfigs {
    314 		if n.Lambda == newLambdaConfig.Arn.String() && newLambdaConfig.Filter == n.Filter {
    315 
    316 			existingConfig := set.NewStringSet()
    317 			for _, v := range n.Events {
    318 				existingConfig.Add(string(v))
    319 			}
    320 
    321 			newConfig := set.NewStringSet()
    322 			for _, v := range lambdaConfig.Events {
    323 				newConfig.Add(string(v))
    324 			}
    325 
    326 			if !newConfig.Intersection(existingConfig).IsEmpty() {
    327 				return false
    328 			}
    329 		}
    330 	}
    331 	b.LambdaConfigs = append(b.LambdaConfigs, newLambdaConfig)
    332 	return true
    333 }
    334 
    335 // RemoveTopicByArn removes all topic configurations that match the exact specified ARN
    336 func (b *Configuration) RemoveTopicByArn(arn Arn) {
    337 	var topics []TopicConfig
    338 	for _, topic := range b.TopicConfigs {
    339 		if topic.Topic != arn.String() {
    340 			topics = append(topics, topic)
    341 		}
    342 	}
    343 	b.TopicConfigs = topics
    344 }
    345 
    346 // ErrNoConfigMatch is returned when a notification configuration (sqs,sns,lambda) is not found when trying to delete
    347 var ErrNoConfigMatch = errors.New("no notification configuration matched")
    348 
    349 // RemoveTopicByArnEventsPrefixSuffix removes a topic configuration that match the exact specified ARN, events, prefix and suffix
    350 func (b *Configuration) RemoveTopicByArnEventsPrefixSuffix(arn Arn, events []EventType, prefix, suffix string) error {
    351 	removeIndex := -1
    352 	for i, v := range b.TopicConfigs {
    353 		// if it matches events and filters, mark the index for deletion
    354 		if v.Topic == arn.String() && v.Config.Equal(events, prefix, suffix) {
    355 			removeIndex = i
    356 			break // since we have at most one matching config
    357 		}
    358 	}
    359 	if removeIndex >= 0 {
    360 		b.TopicConfigs = append(b.TopicConfigs[:removeIndex], b.TopicConfigs[removeIndex+1:]...)
    361 		return nil
    362 	}
    363 	return ErrNoConfigMatch
    364 }
    365 
    366 // RemoveQueueByArn removes all queue configurations that match the exact specified ARN
    367 func (b *Configuration) RemoveQueueByArn(arn Arn) {
    368 	var queues []QueueConfig
    369 	for _, queue := range b.QueueConfigs {
    370 		if queue.Queue != arn.String() {
    371 			queues = append(queues, queue)
    372 		}
    373 	}
    374 	b.QueueConfigs = queues
    375 }
    376 
    377 // RemoveQueueByArnEventsPrefixSuffix removes a queue configuration that match the exact specified ARN, events, prefix and suffix
    378 func (b *Configuration) RemoveQueueByArnEventsPrefixSuffix(arn Arn, events []EventType, prefix, suffix string) error {
    379 	removeIndex := -1
    380 	for i, v := range b.QueueConfigs {
    381 		// if it matches events and filters, mark the index for deletion
    382 		if v.Queue == arn.String() && v.Config.Equal(events, prefix, suffix) {
    383 			removeIndex = i
    384 			break // since we have at most one matching config
    385 		}
    386 	}
    387 	if removeIndex >= 0 {
    388 		b.QueueConfigs = append(b.QueueConfigs[:removeIndex], b.QueueConfigs[removeIndex+1:]...)
    389 		return nil
    390 	}
    391 	return ErrNoConfigMatch
    392 }
    393 
    394 // RemoveLambdaByArn removes all lambda configurations that match the exact specified ARN
    395 func (b *Configuration) RemoveLambdaByArn(arn Arn) {
    396 	var lambdas []LambdaConfig
    397 	for _, lambda := range b.LambdaConfigs {
    398 		if lambda.Lambda != arn.String() {
    399 			lambdas = append(lambdas, lambda)
    400 		}
    401 	}
    402 	b.LambdaConfigs = lambdas
    403 }
    404 
    405 // RemoveLambdaByArnEventsPrefixSuffix removes a topic configuration that match the exact specified ARN, events, prefix and suffix
    406 func (b *Configuration) RemoveLambdaByArnEventsPrefixSuffix(arn Arn, events []EventType, prefix, suffix string) error {
    407 	removeIndex := -1
    408 	for i, v := range b.LambdaConfigs {
    409 		// if it matches events and filters, mark the index for deletion
    410 		if v.Lambda == arn.String() && v.Config.Equal(events, prefix, suffix) {
    411 			removeIndex = i
    412 			break // since we have at most one matching config
    413 		}
    414 	}
    415 	if removeIndex >= 0 {
    416 		b.LambdaConfigs = append(b.LambdaConfigs[:removeIndex], b.LambdaConfigs[removeIndex+1:]...)
    417 		return nil
    418 	}
    419 	return ErrNoConfigMatch
    420 }