1/*
2 * MinIO Go Library for Amazon S3 Compatible Cloud Storage
3 * Copyright 2019 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
18package minio
19
20import (
21	"bytes"
22	"context"
23	"encoding/xml"
24	"fmt"
25	"net/http"
26	"net/url"
27	"time"
28
29	"github.com/minio/minio-go/v7/pkg/s3utils"
30)
31
32// RetentionMode - object retention mode.
33type RetentionMode string
34
35const (
36	// Governance - governance mode.
37	Governance RetentionMode = "GOVERNANCE"
38
39	// Compliance - compliance mode.
40	Compliance RetentionMode = "COMPLIANCE"
41)
42
43func (r RetentionMode) String() string {
44	return string(r)
45}
46
47// IsValid - check whether this retention mode is valid or not.
48func (r RetentionMode) IsValid() bool {
49	return r == Governance || r == Compliance
50}
51
52// ValidityUnit - retention validity unit.
53type ValidityUnit string
54
55const (
56	// Days - denotes no. of days.
57	Days ValidityUnit = "DAYS"
58
59	// Years - denotes no. of years.
60	Years ValidityUnit = "YEARS"
61)
62
63func (unit ValidityUnit) String() string {
64	return string(unit)
65}
66
67// IsValid - check whether this validity unit is valid or not.
68func (unit ValidityUnit) isValid() bool {
69	return unit == Days || unit == Years
70}
71
72// Retention - bucket level retention configuration.
73type Retention struct {
74	Mode     RetentionMode
75	Validity time.Duration
76}
77
78func (r Retention) String() string {
79	return fmt.Sprintf("{Mode:%v, Validity:%v}", r.Mode, r.Validity)
80}
81
82// IsEmpty - returns whether retention is empty or not.
83func (r Retention) IsEmpty() bool {
84	return r.Mode == "" || r.Validity == 0
85}
86
87// objectLockConfig - object lock configuration specified in
88// https://docs.aws.amazon.com/AmazonS3/latest/API/Type_API_ObjectLockConfiguration.html
89type objectLockConfig struct {
90	XMLNS             string   `xml:"xmlns,attr,omitempty"`
91	XMLName           xml.Name `xml:"ObjectLockConfiguration"`
92	ObjectLockEnabled string   `xml:"ObjectLockEnabled"`
93	Rule              *struct {
94		DefaultRetention struct {
95			Mode  RetentionMode `xml:"Mode"`
96			Days  *uint         `xml:"Days"`
97			Years *uint         `xml:"Years"`
98		} `xml:"DefaultRetention"`
99	} `xml:"Rule,omitempty"`
100}
101
102func newObjectLockConfig(mode *RetentionMode, validity *uint, unit *ValidityUnit) (*objectLockConfig, error) {
103	config := &objectLockConfig{
104		ObjectLockEnabled: "Enabled",
105	}
106
107	if mode != nil && validity != nil && unit != nil {
108		if !mode.IsValid() {
109			return nil, fmt.Errorf("invalid retention mode `%v`", mode)
110		}
111
112		if !unit.isValid() {
113			return nil, fmt.Errorf("invalid validity unit `%v`", unit)
114		}
115
116		config.Rule = &struct {
117			DefaultRetention struct {
118				Mode  RetentionMode `xml:"Mode"`
119				Days  *uint         `xml:"Days"`
120				Years *uint         `xml:"Years"`
121			} `xml:"DefaultRetention"`
122		}{}
123
124		config.Rule.DefaultRetention.Mode = *mode
125		if *unit == Days {
126			config.Rule.DefaultRetention.Days = validity
127		} else {
128			config.Rule.DefaultRetention.Years = validity
129		}
130
131		return config, nil
132	}
133
134	if mode == nil && validity == nil && unit == nil {
135		return config, nil
136	}
137
138	return nil, fmt.Errorf("all of retention mode, validity and validity unit must be passed")
139}
140
141// SetBucketObjectLockConfig sets object lock configuration in given bucket. mode, validity and unit are either all set or all nil.
142func (c *Client) SetBucketObjectLockConfig(ctx context.Context, bucketName string, mode *RetentionMode, validity *uint, unit *ValidityUnit) error {
143	// Input validation.
144	if err := s3utils.CheckValidBucketName(bucketName); err != nil {
145		return err
146	}
147
148	// Get resources properly escaped and lined up before
149	// using them in http request.
150	urlValues := make(url.Values)
151	urlValues.Set("object-lock", "")
152
153	config, err := newObjectLockConfig(mode, validity, unit)
154	if err != nil {
155		return err
156	}
157
158	configData, err := xml.Marshal(config)
159	if err != nil {
160		return err
161	}
162
163	reqMetadata := requestMetadata{
164		bucketName:       bucketName,
165		queryValues:      urlValues,
166		contentBody:      bytes.NewReader(configData),
167		contentLength:    int64(len(configData)),
168		contentMD5Base64: sumMD5Base64(configData),
169		contentSHA256Hex: sum256Hex(configData),
170	}
171
172	// Execute PUT bucket object lock configuration.
173	resp, err := c.executeMethod(ctx, http.MethodPut, reqMetadata)
174	defer closeResponse(resp)
175	if err != nil {
176		return err
177	}
178	if resp != nil {
179		if resp.StatusCode != http.StatusOK {
180			return httpRespToErrorResponse(resp, bucketName, "")
181		}
182	}
183	return nil
184}
185
186// GetObjectLockConfig gets object lock configuration of given bucket.
187func (c *Client) GetObjectLockConfig(ctx context.Context, bucketName string) (objectLock string, mode *RetentionMode, validity *uint, unit *ValidityUnit, err error) {
188	// Input validation.
189	if err := s3utils.CheckValidBucketName(bucketName); err != nil {
190		return "", nil, nil, nil, err
191	}
192
193	urlValues := make(url.Values)
194	urlValues.Set("object-lock", "")
195
196	// Execute GET on bucket to list objects.
197	resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{
198		bucketName:       bucketName,
199		queryValues:      urlValues,
200		contentSHA256Hex: emptySHA256Hex,
201	})
202	defer closeResponse(resp)
203	if err != nil {
204		return "", nil, nil, nil, err
205	}
206	if resp != nil {
207		if resp.StatusCode != http.StatusOK {
208			return "", nil, nil, nil, httpRespToErrorResponse(resp, bucketName, "")
209		}
210	}
211	config := &objectLockConfig{}
212	if err = xml.NewDecoder(resp.Body).Decode(config); err != nil {
213		return "", nil, nil, nil, err
214	}
215
216	if config.Rule != nil {
217		mode = &config.Rule.DefaultRetention.Mode
218		if config.Rule.DefaultRetention.Days != nil {
219			validity = config.Rule.DefaultRetention.Days
220			days := Days
221			unit = &days
222		} else {
223			validity = config.Rule.DefaultRetention.Years
224			years := Years
225			unit = &years
226		}
227		return config.ObjectLockEnabled, mode, validity, unit, nil
228	}
229	return config.ObjectLockEnabled, nil, nil, nil, nil
230}
231
232// GetBucketObjectLockConfig gets object lock configuration of given bucket.
233func (c *Client) GetBucketObjectLockConfig(ctx context.Context, bucketName string) (mode *RetentionMode, validity *uint, unit *ValidityUnit, err error) {
234	_, mode, validity, unit, err = c.GetObjectLockConfig(ctx, bucketName)
235	return mode, validity, unit, err
236}
237
238// SetObjectLockConfig sets object lock configuration in given bucket. mode, validity and unit are either all set or all nil.
239func (c *Client) SetObjectLockConfig(ctx context.Context, bucketName string, mode *RetentionMode, validity *uint, unit *ValidityUnit) error {
240	return c.SetBucketObjectLockConfig(ctx, bucketName, mode, validity, unit)
241}
242