You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

320 lines
9.0 KiB

2 years ago
3 months ago
  1. package policy
  2. /*
  3. * MinIO Go Library for Amazon S3 Compatible Cloud Storage
  4. * Copyright 2015-2017 MinIO, Inc.
  5. *
  6. * Licensed under the Apache License, Version 2.0 (the "License");
  7. * you may not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. import (
  19. "encoding/base64"
  20. "fmt"
  21. "github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
  22. "net/http"
  23. "strings"
  24. "time"
  25. )
  26. // expirationDateFormat date format for expiration key in json policy.
  27. const expirationDateFormat = "2006-01-02T15:04:05.999Z"
  28. // policyCondition explanation:
  29. // http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
  30. //
  31. // Example:
  32. //
  33. // policyCondition {
  34. // matchType: "$eq",
  35. // key: "$Content-Type",
  36. // value: "image/png",
  37. // }
  38. type policyCondition struct {
  39. matchType string
  40. condition string
  41. value string
  42. }
  43. // PostPolicy - Provides strict static type conversion and validation
  44. // for Amazon S3's POST policy JSON string.
  45. type PostPolicy struct {
  46. // Expiration date and time of the POST policy.
  47. expiration time.Time
  48. // Collection of different policy conditions.
  49. conditions []policyCondition
  50. // ContentLengthRange minimum and maximum allowable size for the
  51. // uploaded content.
  52. contentLengthRange struct {
  53. min int64
  54. max int64
  55. }
  56. // Post form data.
  57. formData map[string]string
  58. }
  59. // NewPostPolicy - Instantiate new post policy.
  60. func NewPostPolicy() *PostPolicy {
  61. p := &PostPolicy{}
  62. p.conditions = make([]policyCondition, 0)
  63. p.formData = make(map[string]string)
  64. return p
  65. }
  66. // SetExpires - Sets expiration time for the new policy.
  67. func (p *PostPolicy) SetExpires(t time.Time) error {
  68. if t.IsZero() {
  69. return errInvalidArgument("No expiry time set.")
  70. }
  71. p.expiration = t
  72. return nil
  73. }
  74. // SetKey - Sets an object name for the policy based upload.
  75. func (p *PostPolicy) SetKey(key string) error {
  76. if strings.TrimSpace(key) == "" || key == "" {
  77. return errInvalidArgument("Object name is empty.")
  78. }
  79. policyCond := policyCondition{
  80. matchType: "eq",
  81. condition: "$key",
  82. value: key,
  83. }
  84. if err := p.addNewPolicy(policyCond); err != nil {
  85. return err
  86. }
  87. p.formData["key"] = key
  88. return nil
  89. }
  90. // SetKeyStartsWith - Sets an object name that an policy based upload
  91. // can start with.
  92. func (p *PostPolicy) SetKeyStartsWith(keyStartsWith string) error {
  93. if strings.TrimSpace(keyStartsWith) == "" || keyStartsWith == "" {
  94. return errInvalidArgument("Object prefix is empty.")
  95. }
  96. policyCond := policyCondition{
  97. matchType: "starts-with",
  98. condition: "$key",
  99. value: keyStartsWith,
  100. }
  101. if err := p.addNewPolicy(policyCond); err != nil {
  102. return err
  103. }
  104. p.formData["key"] = keyStartsWith
  105. return nil
  106. }
  107. // SetBucket - Sets bucket at which objects will be uploaded to.
  108. func (p *PostPolicy) SetBucket(bucketName string) error {
  109. if strings.TrimSpace(bucketName) == "" || bucketName == "" {
  110. return errInvalidArgument("Bucket name is empty.")
  111. }
  112. policyCond := policyCondition{
  113. matchType: "eq",
  114. condition: "$bucket",
  115. value: bucketName,
  116. }
  117. if err := p.addNewPolicy(policyCond); err != nil {
  118. return err
  119. }
  120. p.formData["bucket"] = bucketName
  121. return nil
  122. }
  123. // SetCondition - Sets condition for credentials, date and algorithm
  124. func (p *PostPolicy) SetCondition(matchType, condition, value string) error {
  125. if strings.TrimSpace(value) == "" || value == "" {
  126. return errInvalidArgument("No value specified for condition")
  127. }
  128. policyCond := policyCondition{
  129. matchType: matchType,
  130. condition: "$" + condition,
  131. value: value,
  132. }
  133. if condition == "X-Amz-Credential" || condition == "X-Amz-Date" || condition == "X-Amz-Algorithm" {
  134. if err := p.addNewPolicy(policyCond); err != nil {
  135. return err
  136. }
  137. p.formData[condition] = value
  138. return nil
  139. }
  140. return errInvalidArgument("Invalid condition in policy")
  141. }
  142. // SetContentType - Sets content-type of the object for this policy
  143. // based upload.
  144. func (p *PostPolicy) SetContentType(contentType string) error {
  145. if strings.TrimSpace(contentType) == "" || contentType == "" {
  146. return errInvalidArgument("No content type specified.")
  147. }
  148. policyCond := policyCondition{
  149. matchType: "eq",
  150. condition: "$Content-Type",
  151. value: contentType,
  152. }
  153. if err := p.addNewPolicy(policyCond); err != nil {
  154. return err
  155. }
  156. p.formData["Content-Type"] = contentType
  157. return nil
  158. }
  159. // SetContentLengthRange - Set new min and max content length
  160. // condition for all incoming uploads.
  161. func (p *PostPolicy) SetContentLengthRange(min, max int64) error {
  162. if min > max {
  163. return errInvalidArgument("Minimum limit is larger than maximum limit.")
  164. }
  165. if min < 0 {
  166. return errInvalidArgument("Minimum limit cannot be negative.")
  167. }
  168. if max < 0 {
  169. return errInvalidArgument("Maximum limit cannot be negative.")
  170. }
  171. p.contentLengthRange.min = min
  172. p.contentLengthRange.max = max
  173. return nil
  174. }
  175. // SetSuccessActionRedirect - Sets the redirect success url of the object for this policy
  176. // based upload.
  177. func (p *PostPolicy) SetSuccessActionRedirect(redirect string) error {
  178. if strings.TrimSpace(redirect) == "" || redirect == "" {
  179. return errInvalidArgument("Redirect is empty")
  180. }
  181. policyCond := policyCondition{
  182. matchType: "eq",
  183. condition: "$success_action_redirect",
  184. value: redirect,
  185. }
  186. if err := p.addNewPolicy(policyCond); err != nil {
  187. return err
  188. }
  189. p.formData["success_action_redirect"] = redirect
  190. return nil
  191. }
  192. // SetSuccessStatusAction - Sets the status success code of the object for this policy
  193. // based upload.
  194. func (p *PostPolicy) SetSuccessStatusAction(status string) error {
  195. if strings.TrimSpace(status) == "" || status == "" {
  196. return errInvalidArgument("Status is empty")
  197. }
  198. policyCond := policyCondition{
  199. matchType: "eq",
  200. condition: "$success_action_status",
  201. value: status,
  202. }
  203. if err := p.addNewPolicy(policyCond); err != nil {
  204. return err
  205. }
  206. p.formData["success_action_status"] = status
  207. return nil
  208. }
  209. // SetUserMetadata - Set user metadata as a key/value couple.
  210. // Can be retrieved through a HEAD request or an event.
  211. func (p *PostPolicy) SetUserMetadata(key string, value string) error {
  212. if strings.TrimSpace(key) == "" || key == "" {
  213. return errInvalidArgument("Key is empty")
  214. }
  215. if strings.TrimSpace(value) == "" || value == "" {
  216. return errInvalidArgument("Value is empty")
  217. }
  218. headerName := fmt.Sprintf("x-amz-meta-%s", key)
  219. policyCond := policyCondition{
  220. matchType: "eq",
  221. condition: fmt.Sprintf("$%s", headerName),
  222. value: value,
  223. }
  224. if err := p.addNewPolicy(policyCond); err != nil {
  225. return err
  226. }
  227. p.formData[headerName] = value
  228. return nil
  229. }
  230. // SetUserData - Set user data as a key/value couple.
  231. // Can be retrieved through a HEAD request or an event.
  232. func (p *PostPolicy) SetUserData(key string, value string) error {
  233. if key == "" {
  234. return errInvalidArgument("Key is empty")
  235. }
  236. if value == "" {
  237. return errInvalidArgument("Value is empty")
  238. }
  239. headerName := fmt.Sprintf("x-amz-%s", key)
  240. policyCond := policyCondition{
  241. matchType: "eq",
  242. condition: fmt.Sprintf("$%s", headerName),
  243. value: value,
  244. }
  245. if err := p.addNewPolicy(policyCond); err != nil {
  246. return err
  247. }
  248. p.formData[headerName] = value
  249. return nil
  250. }
  251. // addNewPolicy - internal helper to validate adding new policies.
  252. func (p *PostPolicy) addNewPolicy(policyCond policyCondition) error {
  253. if policyCond.matchType == "" || policyCond.condition == "" || policyCond.value == "" {
  254. return errInvalidArgument("Policy fields are empty.")
  255. }
  256. p.conditions = append(p.conditions, policyCond)
  257. return nil
  258. }
  259. // String function for printing policy in json formatted string.
  260. func (p PostPolicy) String() string {
  261. return string(p.marshalJSON())
  262. }
  263. // marshalJSON - Provides Marshaled JSON in bytes.
  264. func (p PostPolicy) marshalJSON() []byte {
  265. expirationStr := `"expiration":"` + p.expiration.Format(expirationDateFormat) + `"`
  266. var conditionsStr string
  267. conditions := []string{}
  268. for _, po := range p.conditions {
  269. conditions = append(conditions, fmt.Sprintf("[\"%s\",\"%s\",\"%s\"]", po.matchType, po.condition, po.value))
  270. }
  271. if p.contentLengthRange.min != 0 || p.contentLengthRange.max != 0 {
  272. conditions = append(conditions, fmt.Sprintf("[\"content-length-range\", %d, %d]",
  273. p.contentLengthRange.min, p.contentLengthRange.max))
  274. }
  275. if len(conditions) > 0 {
  276. conditionsStr = `"conditions":[` + strings.Join(conditions, ",") + "]"
  277. }
  278. retStr := "{"
  279. retStr = retStr + expirationStr + ","
  280. retStr = retStr + conditionsStr
  281. retStr = retStr + "}"
  282. return []byte(retStr)
  283. }
  284. // base64 - Produces base64 of PostPolicy's Marshaled json.
  285. func (p PostPolicy) base64() string {
  286. return base64.StdEncoding.EncodeToString(p.marshalJSON())
  287. }
  288. // errInvalidArgument - Invalid argument response.
  289. func errInvalidArgument(message string) error {
  290. return s3err.RESTErrorResponse{
  291. StatusCode: http.StatusBadRequest,
  292. Code: "InvalidArgument",
  293. Message: message,
  294. RequestID: "client",
  295. }
  296. }