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.

321 lines
9.0 KiB

  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. //
  39. type policyCondition struct {
  40. matchType string
  41. condition string
  42. value string
  43. }
  44. // PostPolicy - Provides strict static type conversion and validation
  45. // for Amazon S3's POST policy JSON string.
  46. type PostPolicy struct {
  47. // Expiration date and time of the POST policy.
  48. expiration time.Time
  49. // Collection of different policy conditions.
  50. conditions []policyCondition
  51. // ContentLengthRange minimum and maximum allowable size for the
  52. // uploaded content.
  53. contentLengthRange struct {
  54. min int64
  55. max int64
  56. }
  57. // Post form data.
  58. formData map[string]string
  59. }
  60. // NewPostPolicy - Instantiate new post policy.
  61. func NewPostPolicy() *PostPolicy {
  62. p := &PostPolicy{}
  63. p.conditions = make([]policyCondition, 0)
  64. p.formData = make(map[string]string)
  65. return p
  66. }
  67. // SetExpires - Sets expiration time for the new policy.
  68. func (p *PostPolicy) SetExpires(t time.Time) error {
  69. if t.IsZero() {
  70. return errInvalidArgument("No expiry time set.")
  71. }
  72. p.expiration = t
  73. return nil
  74. }
  75. // SetKey - Sets an object name for the policy based upload.
  76. func (p *PostPolicy) SetKey(key string) error {
  77. if strings.TrimSpace(key) == "" || key == "" {
  78. return errInvalidArgument("Object name is empty.")
  79. }
  80. policyCond := policyCondition{
  81. matchType: "eq",
  82. condition: "$key",
  83. value: key,
  84. }
  85. if err := p.addNewPolicy(policyCond); err != nil {
  86. return err
  87. }
  88. p.formData["key"] = key
  89. return nil
  90. }
  91. // SetKeyStartsWith - Sets an object name that an policy based upload
  92. // can start with.
  93. func (p *PostPolicy) SetKeyStartsWith(keyStartsWith string) error {
  94. if strings.TrimSpace(keyStartsWith) == "" || keyStartsWith == "" {
  95. return errInvalidArgument("Object prefix is empty.")
  96. }
  97. policyCond := policyCondition{
  98. matchType: "starts-with",
  99. condition: "$key",
  100. value: keyStartsWith,
  101. }
  102. if err := p.addNewPolicy(policyCond); err != nil {
  103. return err
  104. }
  105. p.formData["key"] = keyStartsWith
  106. return nil
  107. }
  108. // SetBucket - Sets bucket at which objects will be uploaded to.
  109. func (p *PostPolicy) SetBucket(bucketName string) error {
  110. if strings.TrimSpace(bucketName) == "" || bucketName == "" {
  111. return errInvalidArgument("Bucket name is empty.")
  112. }
  113. policyCond := policyCondition{
  114. matchType: "eq",
  115. condition: "$bucket",
  116. value: bucketName,
  117. }
  118. if err := p.addNewPolicy(policyCond); err != nil {
  119. return err
  120. }
  121. p.formData["bucket"] = bucketName
  122. return nil
  123. }
  124. // SetCondition - Sets condition for credentials, date and algorithm
  125. func (p *PostPolicy) SetCondition(matchType, condition, value string) error {
  126. if strings.TrimSpace(value) == "" || value == "" {
  127. return errInvalidArgument("No value specified for condition")
  128. }
  129. policyCond := policyCondition{
  130. matchType: matchType,
  131. condition: "$" + condition,
  132. value: value,
  133. }
  134. if condition == "X-Amz-Credential" || condition == "X-Amz-Date" || condition == "X-Amz-Algorithm" {
  135. if err := p.addNewPolicy(policyCond); err != nil {
  136. return err
  137. }
  138. p.formData[condition] = value
  139. return nil
  140. }
  141. return errInvalidArgument("Invalid condition in policy")
  142. }
  143. // SetContentType - Sets content-type of the object for this policy
  144. // based upload.
  145. func (p *PostPolicy) SetContentType(contentType string) error {
  146. if strings.TrimSpace(contentType) == "" || contentType == "" {
  147. return errInvalidArgument("No content type specified.")
  148. }
  149. policyCond := policyCondition{
  150. matchType: "eq",
  151. condition: "$Content-Type",
  152. value: contentType,
  153. }
  154. if err := p.addNewPolicy(policyCond); err != nil {
  155. return err
  156. }
  157. p.formData["Content-Type"] = contentType
  158. return nil
  159. }
  160. // SetContentLengthRange - Set new min and max content length
  161. // condition for all incoming uploads.
  162. func (p *PostPolicy) SetContentLengthRange(min, max int64) error {
  163. if min > max {
  164. return errInvalidArgument("Minimum limit is larger than maximum limit.")
  165. }
  166. if min < 0 {
  167. return errInvalidArgument("Minimum limit cannot be negative.")
  168. }
  169. if max < 0 {
  170. return errInvalidArgument("Maximum limit cannot be negative.")
  171. }
  172. p.contentLengthRange.min = min
  173. p.contentLengthRange.max = max
  174. return nil
  175. }
  176. // SetSuccessActionRedirect - Sets the redirect success url of the object for this policy
  177. // based upload.
  178. func (p *PostPolicy) SetSuccessActionRedirect(redirect string) error {
  179. if strings.TrimSpace(redirect) == "" || redirect == "" {
  180. return errInvalidArgument("Redirect is empty")
  181. }
  182. policyCond := policyCondition{
  183. matchType: "eq",
  184. condition: "$success_action_redirect",
  185. value: redirect,
  186. }
  187. if err := p.addNewPolicy(policyCond); err != nil {
  188. return err
  189. }
  190. p.formData["success_action_redirect"] = redirect
  191. return nil
  192. }
  193. // SetSuccessStatusAction - Sets the status success code of the object for this policy
  194. // based upload.
  195. func (p *PostPolicy) SetSuccessStatusAction(status string) error {
  196. if strings.TrimSpace(status) == "" || status == "" {
  197. return errInvalidArgument("Status is empty")
  198. }
  199. policyCond := policyCondition{
  200. matchType: "eq",
  201. condition: "$success_action_status",
  202. value: status,
  203. }
  204. if err := p.addNewPolicy(policyCond); err != nil {
  205. return err
  206. }
  207. p.formData["success_action_status"] = status
  208. return nil
  209. }
  210. // SetUserMetadata - Set user metadata as a key/value couple.
  211. // Can be retrieved through a HEAD request or an event.
  212. func (p *PostPolicy) SetUserMetadata(key string, value string) error {
  213. if strings.TrimSpace(key) == "" || key == "" {
  214. return errInvalidArgument("Key is empty")
  215. }
  216. if strings.TrimSpace(value) == "" || value == "" {
  217. return errInvalidArgument("Value is empty")
  218. }
  219. headerName := fmt.Sprintf("x-amz-meta-%s", key)
  220. policyCond := policyCondition{
  221. matchType: "eq",
  222. condition: fmt.Sprintf("$%s", headerName),
  223. value: value,
  224. }
  225. if err := p.addNewPolicy(policyCond); err != nil {
  226. return err
  227. }
  228. p.formData[headerName] = value
  229. return nil
  230. }
  231. // SetUserData - Set user data as a key/value couple.
  232. // Can be retrieved through a HEAD request or an event.
  233. func (p *PostPolicy) SetUserData(key string, value string) error {
  234. if key == "" {
  235. return errInvalidArgument("Key is empty")
  236. }
  237. if value == "" {
  238. return errInvalidArgument("Value is empty")
  239. }
  240. headerName := fmt.Sprintf("x-amz-%s", key)
  241. policyCond := policyCondition{
  242. matchType: "eq",
  243. condition: fmt.Sprintf("$%s", headerName),
  244. value: value,
  245. }
  246. if err := p.addNewPolicy(policyCond); err != nil {
  247. return err
  248. }
  249. p.formData[headerName] = value
  250. return nil
  251. }
  252. // addNewPolicy - internal helper to validate adding new policies.
  253. func (p *PostPolicy) addNewPolicy(policyCond policyCondition) error {
  254. if policyCond.matchType == "" || policyCond.condition == "" || policyCond.value == "" {
  255. return errInvalidArgument("Policy fields are empty.")
  256. }
  257. p.conditions = append(p.conditions, policyCond)
  258. return nil
  259. }
  260. // String function for printing policy in json formatted string.
  261. func (p PostPolicy) String() string {
  262. return string(p.marshalJSON())
  263. }
  264. // marshalJSON - Provides Marshaled JSON in bytes.
  265. func (p PostPolicy) marshalJSON() []byte {
  266. expirationStr := `"expiration":"` + p.expiration.Format(expirationDateFormat) + `"`
  267. var conditionsStr string
  268. conditions := []string{}
  269. for _, po := range p.conditions {
  270. conditions = append(conditions, fmt.Sprintf("[\"%s\",\"%s\",\"%s\"]", po.matchType, po.condition, po.value))
  271. }
  272. if p.contentLengthRange.min != 0 || p.contentLengthRange.max != 0 {
  273. conditions = append(conditions, fmt.Sprintf("[\"content-length-range\", %d, %d]",
  274. p.contentLengthRange.min, p.contentLengthRange.max))
  275. }
  276. if len(conditions) > 0 {
  277. conditionsStr = `"conditions":[` + strings.Join(conditions, ",") + "]"
  278. }
  279. retStr := "{"
  280. retStr = retStr + expirationStr + ","
  281. retStr = retStr + conditionsStr
  282. retStr = retStr + "}"
  283. return []byte(retStr)
  284. }
  285. // base64 - Produces base64 of PostPolicy's Marshaled json.
  286. func (p PostPolicy) base64() string {
  287. return base64.StdEncoding.EncodeToString(p.marshalJSON())
  288. }
  289. // errInvalidArgument - Invalid argument response.
  290. func errInvalidArgument(message string) error {
  291. return s3err.RESTErrorResponse{
  292. StatusCode: http.StatusBadRequest,
  293. Code: "InvalidArgument",
  294. Message: message,
  295. RequestID: "minio",
  296. }
  297. }