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.

1017 lines
28 KiB

fs: Break fs package to top-level and introduce ObjectAPI interface. ObjectAPI interface brings in changes needed for XL ObjectAPI layer. The new interface for any ObjectAPI layer is as below ``` // ObjectAPI interface. type ObjectAPI interface { // Bucket resource API. DeleteBucket(bucket string) *probe.Error ListBuckets() ([]BucketInfo, *probe.Error) MakeBucket(bucket string) *probe.Error GetBucketInfo(bucket string) (BucketInfo, *probe.Error) // Bucket query API. ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsResult, *probe.Error) ListMultipartUploads(bucket string, resources BucketMultipartResourcesMetadata) (BucketMultipartResourcesMetadata, *probe.Error) // Object resource API. GetObject(bucket, object string, startOffset int64) (io.ReadCloser, *probe.Error) GetObjectInfo(bucket, object string) (ObjectInfo, *probe.Error) PutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string) (ObjectInfo, *probe.Error) DeleteObject(bucket, object string) *probe.Error // Object query API. NewMultipartUpload(bucket, object string) (string, *probe.Error) PutObjectPart(bucket, object, uploadID string, partID int, size int64, data io.Reader, md5Hex string) (string, *probe.Error) ListObjectParts(bucket, object string, resources ObjectResourcesMetadata) (ObjectResourcesMetadata, *probe.Error) CompleteMultipartUpload(bucket string, object string, uploadID string, parts []CompletePart) (ObjectInfo, *probe.Error) AbortMultipartUpload(bucket, object, uploadID string) *probe.Error } ```
9 years ago
  1. /*
  2. * Minio Cloud Storage, (C) 2016 Minio, Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package cmd
  17. import (
  18. "archive/zip"
  19. "encoding/json"
  20. "errors"
  21. "fmt"
  22. "io/ioutil"
  23. "net/http"
  24. "os"
  25. "path"
  26. "runtime"
  27. "strconv"
  28. "strings"
  29. "time"
  30. "github.com/dustin/go-humanize"
  31. "github.com/gorilla/mux"
  32. "github.com/gorilla/rpc/v2/json2"
  33. "github.com/minio/minio-go/pkg/policy"
  34. "github.com/minio/minio/browser"
  35. )
  36. // WebGenericArgs - empty struct for calls that don't accept arguments
  37. // for ex. ServerInfo, GenerateAuth
  38. type WebGenericArgs struct{}
  39. // WebGenericRep - reply structure for calls for which reply is success/failure
  40. // for ex. RemoveObject MakeBucket
  41. type WebGenericRep struct {
  42. UIVersion string `json:"uiVersion"`
  43. }
  44. // ServerInfoRep - server info reply.
  45. type ServerInfoRep struct {
  46. MinioVersion string
  47. MinioMemory string
  48. MinioPlatform string
  49. MinioRuntime string
  50. MinioEnvVars []string
  51. UIVersion string `json:"uiVersion"`
  52. }
  53. // ServerInfo - get server info.
  54. func (web *webAPIHandlers) ServerInfo(r *http.Request, args *WebGenericArgs, reply *ServerInfoRep) error {
  55. if !isHTTPRequestValid(r) {
  56. return toJSONError(errAuthentication)
  57. }
  58. host, err := os.Hostname()
  59. if err != nil {
  60. host = ""
  61. }
  62. memstats := &runtime.MemStats{}
  63. runtime.ReadMemStats(memstats)
  64. mem := fmt.Sprintf("Used: %s | Allocated: %s | Used-Heap: %s | Allocated-Heap: %s",
  65. humanize.Bytes(memstats.Alloc),
  66. humanize.Bytes(memstats.TotalAlloc),
  67. humanize.Bytes(memstats.HeapAlloc),
  68. humanize.Bytes(memstats.HeapSys))
  69. platform := fmt.Sprintf("Host: %s | OS: %s | Arch: %s",
  70. host,
  71. runtime.GOOS,
  72. runtime.GOARCH)
  73. goruntime := fmt.Sprintf("Version: %s | CPUs: %s", runtime.Version(), strconv.Itoa(runtime.NumCPU()))
  74. reply.MinioEnvVars = os.Environ()
  75. reply.MinioVersion = Version
  76. reply.MinioMemory = mem
  77. reply.MinioPlatform = platform
  78. reply.MinioRuntime = goruntime
  79. reply.UIVersion = browser.UIVersion
  80. return nil
  81. }
  82. // StorageInfoRep - contains storage usage statistics.
  83. type StorageInfoRep struct {
  84. StorageInfo StorageInfo `json:"storageInfo"`
  85. UIVersion string `json:"uiVersion"`
  86. }
  87. // StorageInfo - web call to gather storage usage statistics.
  88. func (web *webAPIHandlers) StorageInfo(r *http.Request, args *AuthRPCArgs, reply *StorageInfoRep) error {
  89. objectAPI := web.ObjectAPI()
  90. if objectAPI == nil {
  91. return toJSONError(errServerNotInitialized)
  92. }
  93. if !isHTTPRequestValid(r) {
  94. return toJSONError(errAuthentication)
  95. }
  96. reply.StorageInfo = objectAPI.StorageInfo()
  97. reply.UIVersion = browser.UIVersion
  98. return nil
  99. }
  100. // MakeBucketArgs - make bucket args.
  101. type MakeBucketArgs struct {
  102. BucketName string `json:"bucketName"`
  103. }
  104. // MakeBucket - creates a new bucket.
  105. func (web *webAPIHandlers) MakeBucket(r *http.Request, args *MakeBucketArgs, reply *WebGenericRep) error {
  106. objectAPI := web.ObjectAPI()
  107. if objectAPI == nil {
  108. return toJSONError(errServerNotInitialized)
  109. }
  110. if !isHTTPRequestValid(r) {
  111. return toJSONError(errAuthentication)
  112. }
  113. // Check if bucket is a reserved bucket name.
  114. if isMinioMetaBucket(args.BucketName) || isMinioReservedBucket(args.BucketName) {
  115. return toJSONError(errReservedBucket)
  116. }
  117. bucketLock := globalNSMutex.NewNSLock(args.BucketName, "")
  118. bucketLock.Lock()
  119. defer bucketLock.Unlock()
  120. if err := objectAPI.MakeBucket(args.BucketName); err != nil {
  121. return toJSONError(err, args.BucketName)
  122. }
  123. reply.UIVersion = browser.UIVersion
  124. return nil
  125. }
  126. // ListBucketsRep - list buckets response
  127. type ListBucketsRep struct {
  128. Buckets []WebBucketInfo `json:"buckets"`
  129. UIVersion string `json:"uiVersion"`
  130. }
  131. // WebBucketInfo container for list buckets metadata.
  132. type WebBucketInfo struct {
  133. // The name of the bucket.
  134. Name string `json:"name"`
  135. // Date the bucket was created.
  136. CreationDate time.Time `json:"creationDate"`
  137. }
  138. // ListBuckets - list buckets api.
  139. func (web *webAPIHandlers) ListBuckets(r *http.Request, args *WebGenericArgs, reply *ListBucketsRep) error {
  140. objectAPI := web.ObjectAPI()
  141. if objectAPI == nil {
  142. return toJSONError(errServerNotInitialized)
  143. }
  144. authErr := webRequestAuthenticate(r)
  145. if authErr != nil {
  146. return toJSONError(authErr)
  147. }
  148. buckets, err := objectAPI.ListBuckets()
  149. if err != nil {
  150. return toJSONError(err)
  151. }
  152. for _, bucket := range buckets {
  153. reply.Buckets = append(reply.Buckets, WebBucketInfo{
  154. Name: bucket.Name,
  155. CreationDate: bucket.Created,
  156. })
  157. }
  158. reply.UIVersion = browser.UIVersion
  159. return nil
  160. }
  161. // ListObjectsArgs - list object args.
  162. type ListObjectsArgs struct {
  163. BucketName string `json:"bucketName"`
  164. Prefix string `json:"prefix"`
  165. Marker string `json:"marker"`
  166. }
  167. // ListObjectsRep - list objects response.
  168. type ListObjectsRep struct {
  169. Objects []WebObjectInfo `json:"objects"`
  170. NextMarker string `json:"nextmarker"`
  171. IsTruncated bool `json:"istruncated"`
  172. Writable bool `json:"writable"` // Used by client to show "upload file" button.
  173. UIVersion string `json:"uiVersion"`
  174. }
  175. // WebObjectInfo container for list objects metadata.
  176. type WebObjectInfo struct {
  177. // Name of the object
  178. Key string `json:"name"`
  179. // Date and time the object was last modified.
  180. LastModified time.Time `json:"lastModified"`
  181. // Size in bytes of the object.
  182. Size int64 `json:"size"`
  183. // ContentType is mime type of the object.
  184. ContentType string `json:"contentType"`
  185. }
  186. // ListObjects - list objects api.
  187. func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, reply *ListObjectsRep) error {
  188. reply.UIVersion = browser.UIVersion
  189. objectAPI := web.ObjectAPI()
  190. if objectAPI == nil {
  191. return toJSONError(errServerNotInitialized)
  192. }
  193. prefix := args.Prefix + "test" // To test if GetObject/PutObject with the specified prefix is allowed.
  194. readable := isBucketActionAllowed("s3:GetObject", args.BucketName, prefix)
  195. writable := isBucketActionAllowed("s3:PutObject", args.BucketName, prefix)
  196. authErr := webRequestAuthenticate(r)
  197. switch {
  198. case authErr == errAuthentication:
  199. return toJSONError(authErr)
  200. case authErr == nil:
  201. break
  202. case readable && writable:
  203. reply.Writable = true
  204. break
  205. case readable:
  206. break
  207. case writable:
  208. reply.Writable = true
  209. return nil
  210. default:
  211. return errAuthentication
  212. }
  213. lo, err := objectAPI.ListObjects(args.BucketName, args.Prefix, args.Marker, slashSeparator, 1000)
  214. if err != nil {
  215. return &json2.Error{Message: err.Error()}
  216. }
  217. reply.NextMarker = lo.NextMarker
  218. reply.IsTruncated = lo.IsTruncated
  219. for _, obj := range lo.Objects {
  220. reply.Objects = append(reply.Objects, WebObjectInfo{
  221. Key: obj.Name,
  222. LastModified: obj.ModTime,
  223. Size: obj.Size,
  224. ContentType: obj.ContentType,
  225. })
  226. }
  227. for _, prefix := range lo.Prefixes {
  228. reply.Objects = append(reply.Objects, WebObjectInfo{
  229. Key: prefix,
  230. })
  231. }
  232. return nil
  233. }
  234. // RemoveObjectArgs - args to remove an object
  235. // JSON will look like:
  236. // '{"bucketname":"testbucket","objects":["photos/hawaii/","photos/maldives/","photos/sanjose.jpg"]}'
  237. type RemoveObjectArgs struct {
  238. Objects []string `json:"objects"` // can be files or sub-directories
  239. Prefix string `json:"prefix"` // current directory in the browser-ui
  240. BucketName string `json:"bucketname"` // bucket name.
  241. }
  242. // RemoveObject - removes an object.
  243. func (web *webAPIHandlers) RemoveObject(r *http.Request, args *RemoveObjectArgs, reply *WebGenericRep) error {
  244. objectAPI := web.ObjectAPI()
  245. if objectAPI == nil {
  246. return toJSONError(errServerNotInitialized)
  247. }
  248. if !isHTTPRequestValid(r) {
  249. return toJSONError(errAuthentication)
  250. }
  251. if args.BucketName == "" || len(args.Objects) == 0 {
  252. return toJSONError(errUnexpected)
  253. }
  254. var err error
  255. objectLoop:
  256. for _, object := range args.Objects {
  257. remove := func(objectName string) error {
  258. objectLock := globalNSMutex.NewNSLock(args.BucketName, objectName)
  259. objectLock.Lock()
  260. defer objectLock.Unlock()
  261. err = objectAPI.DeleteObject(args.BucketName, objectName)
  262. if err == nil {
  263. // Notify object deleted event.
  264. eventNotify(eventData{
  265. Type: ObjectRemovedDelete,
  266. Bucket: args.BucketName,
  267. ObjInfo: ObjectInfo{
  268. Name: objectName,
  269. },
  270. ReqParams: extractReqParams(r),
  271. })
  272. }
  273. return err
  274. }
  275. if !hasSuffix(object, slashSeparator) {
  276. // If not a directory, remove the object.
  277. err = remove(object)
  278. if err != nil {
  279. break objectLoop
  280. }
  281. continue
  282. }
  283. // For directories, list the contents recursively and remove.
  284. marker := ""
  285. for {
  286. var lo ListObjectsInfo
  287. lo, err = objectAPI.ListObjects(args.BucketName, object, marker, "", 1000)
  288. if err != nil {
  289. break objectLoop
  290. }
  291. marker = lo.NextMarker
  292. for _, obj := range lo.Objects {
  293. err = remove(obj.Name)
  294. if err != nil {
  295. break objectLoop
  296. }
  297. }
  298. if !lo.IsTruncated {
  299. break
  300. }
  301. }
  302. }
  303. if err != nil && !isErrObjectNotFound(err) {
  304. // Ignore object not found error.
  305. return toJSONError(err, args.BucketName, "")
  306. }
  307. reply.UIVersion = browser.UIVersion
  308. return nil
  309. }
  310. // LoginArgs - login arguments.
  311. type LoginArgs struct {
  312. Username string `json:"username" form:"username"`
  313. Password string `json:"password" form:"password"`
  314. }
  315. // LoginRep - login reply.
  316. type LoginRep struct {
  317. Token string `json:"token"`
  318. UIVersion string `json:"uiVersion"`
  319. }
  320. // Login - user login handler.
  321. func (web *webAPIHandlers) Login(r *http.Request, args *LoginArgs, reply *LoginRep) error {
  322. token, err := authenticateWeb(args.Username, args.Password)
  323. if err != nil {
  324. // Make sure to log errors related to browser login,
  325. // for security and auditing reasons.
  326. errorIf(err, "Unable to login request from %s", r.RemoteAddr)
  327. return toJSONError(err)
  328. }
  329. reply.Token = token
  330. reply.UIVersion = browser.UIVersion
  331. return nil
  332. }
  333. // GenerateAuthReply - reply for GenerateAuth
  334. type GenerateAuthReply struct {
  335. AccessKey string `json:"accessKey"`
  336. SecretKey string `json:"secretKey"`
  337. UIVersion string `json:"uiVersion"`
  338. }
  339. func (web webAPIHandlers) GenerateAuth(r *http.Request, args *WebGenericArgs, reply *GenerateAuthReply) error {
  340. if !isHTTPRequestValid(r) {
  341. return toJSONError(errAuthentication)
  342. }
  343. cred := mustGetNewCredential()
  344. reply.AccessKey = cred.AccessKey
  345. reply.SecretKey = cred.SecretKey
  346. reply.UIVersion = browser.UIVersion
  347. return nil
  348. }
  349. // SetAuthArgs - argument for SetAuth
  350. type SetAuthArgs struct {
  351. AccessKey string `json:"accessKey"`
  352. SecretKey string `json:"secretKey"`
  353. }
  354. // SetAuthReply - reply for SetAuth
  355. type SetAuthReply struct {
  356. Token string `json:"token"`
  357. UIVersion string `json:"uiVersion"`
  358. PeerErrMsgs map[string]string `json:"peerErrMsgs"`
  359. }
  360. // SetAuth - Set accessKey and secretKey credentials.
  361. func (web *webAPIHandlers) SetAuth(r *http.Request, args *SetAuthArgs, reply *SetAuthReply) error {
  362. if !isHTTPRequestValid(r) {
  363. return toJSONError(errAuthentication)
  364. }
  365. // If creds are set through ENV disallow changing credentials.
  366. if globalIsEnvCreds {
  367. return toJSONError(errChangeCredNotAllowed)
  368. }
  369. creds, err := createCredential(args.AccessKey, args.SecretKey)
  370. if err != nil {
  371. return toJSONError(err)
  372. }
  373. // Notify all other Minio peers to update credentials
  374. errsMap := updateCredsOnPeers(creds)
  375. // Update local credentials
  376. serverConfig.SetCredential(creds)
  377. // Persist updated credentials.
  378. if err = serverConfig.Save(); err != nil {
  379. errsMap[globalMinioAddr] = err
  380. }
  381. // Log all the peer related error messages, and populate the
  382. // PeerErrMsgs map.
  383. reply.PeerErrMsgs = make(map[string]string)
  384. for svr, errVal := range errsMap {
  385. tErr := fmt.Errorf("Unable to change credentials on %s: %v", svr, errVal)
  386. errorIf(tErr, "Credentials change could not be propagated successfully!")
  387. reply.PeerErrMsgs[svr] = errVal.Error()
  388. }
  389. // If we were unable to update locally, we return an error to the user/browser.
  390. if errsMap[globalMinioAddr] != nil {
  391. // Since the error message may be very long to display
  392. // on the browser, we tell the user to check the
  393. // server logs.
  394. return toJSONError(errors.New("unexpected error(s) occurred - please check minio server logs"))
  395. }
  396. // As we have updated access/secret key, generate new auth token.
  397. token, err := authenticateWeb(creds.AccessKey, creds.SecretKey)
  398. if err != nil {
  399. // Did we have peer errors?
  400. if len(errsMap) > 0 {
  401. err = fmt.Errorf(
  402. "we gave up due to: '%s', but there were more errors. Please check minio server logs",
  403. err.Error(),
  404. )
  405. }
  406. return toJSONError(err)
  407. }
  408. reply.Token = token
  409. reply.UIVersion = browser.UIVersion
  410. return nil
  411. }
  412. // GetAuthReply - Reply current credentials.
  413. type GetAuthReply struct {
  414. AccessKey string `json:"accessKey"`
  415. SecretKey string `json:"secretKey"`
  416. UIVersion string `json:"uiVersion"`
  417. }
  418. // GetAuth - return accessKey and secretKey credentials.
  419. func (web *webAPIHandlers) GetAuth(r *http.Request, args *WebGenericArgs, reply *GetAuthReply) error {
  420. if !isHTTPRequestValid(r) {
  421. return toJSONError(errAuthentication)
  422. }
  423. creds := serverConfig.GetCredential()
  424. reply.AccessKey = creds.AccessKey
  425. reply.SecretKey = creds.SecretKey
  426. reply.UIVersion = browser.UIVersion
  427. return nil
  428. }
  429. // Upload - file upload handler.
  430. func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
  431. objectAPI := web.ObjectAPI()
  432. if objectAPI == nil {
  433. writeWebErrorResponse(w, errServerNotInitialized)
  434. return
  435. }
  436. vars := mux.Vars(r)
  437. bucket := vars["bucket"]
  438. object := vars["object"]
  439. authErr := webRequestAuthenticate(r)
  440. if authErr == errAuthentication {
  441. writeWebErrorResponse(w, errAuthentication)
  442. return
  443. }
  444. if authErr != nil && !isBucketActionAllowed("s3:PutObject", bucket, object) {
  445. writeWebErrorResponse(w, errAuthentication)
  446. return
  447. }
  448. // Require Content-Length to be set in the request
  449. size := r.ContentLength
  450. if size < 0 {
  451. writeWebErrorResponse(w, errSizeUnspecified)
  452. return
  453. }
  454. // Extract incoming metadata if any.
  455. metadata := extractMetadataFromHeader(r.Header)
  456. // Lock the object.
  457. objectLock := globalNSMutex.NewNSLock(bucket, object)
  458. objectLock.Lock()
  459. defer objectLock.Unlock()
  460. sha256sum := ""
  461. objInfo, err := objectAPI.PutObject(bucket, object, size, r.Body, metadata, sha256sum)
  462. if err != nil {
  463. writeWebErrorResponse(w, err)
  464. return
  465. }
  466. // Notify object created event.
  467. eventNotify(eventData{
  468. Type: ObjectCreatedPut,
  469. Bucket: bucket,
  470. ObjInfo: objInfo,
  471. ReqParams: extractReqParams(r),
  472. })
  473. }
  474. // Download - file download handler.
  475. func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) {
  476. objectAPI := web.ObjectAPI()
  477. if objectAPI == nil {
  478. writeWebErrorResponse(w, errServerNotInitialized)
  479. return
  480. }
  481. vars := mux.Vars(r)
  482. bucket := vars["bucket"]
  483. object := vars["object"]
  484. token := r.URL.Query().Get("token")
  485. if !isAuthTokenValid(token) && !isBucketActionAllowed("s3:GetObject", bucket, object) {
  486. writeWebErrorResponse(w, errAuthentication)
  487. return
  488. }
  489. // Add content disposition.
  490. w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", path.Base(object)))
  491. // Lock the object before reading.
  492. objectLock := globalNSMutex.NewNSLock(bucket, object)
  493. objectLock.RLock()
  494. defer objectLock.RUnlock()
  495. if err := objectAPI.GetObject(bucket, object, 0, -1, w); err != nil {
  496. /// No need to print error, response writer already written to.
  497. return
  498. }
  499. }
  500. // DownloadZipArgs - Argument for downloading a bunch of files as a zip file.
  501. // JSON will look like:
  502. // '{"bucketname":"testbucket","prefix":"john/pics/","objects":["hawaii/","maldives/","sanjose.jpg"]}'
  503. type DownloadZipArgs struct {
  504. Objects []string `json:"objects"` // can be files or sub-directories
  505. Prefix string `json:"prefix"` // current directory in the browser-ui
  506. BucketName string `json:"bucketname"` // bucket name.
  507. }
  508. // Takes a list of objects and creates a zip file that sent as the response body.
  509. func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) {
  510. objectAPI := web.ObjectAPI()
  511. if objectAPI == nil {
  512. writeWebErrorResponse(w, errServerNotInitialized)
  513. return
  514. }
  515. token := r.URL.Query().Get("token")
  516. if !isAuthTokenValid(token) {
  517. writeWebErrorResponse(w, errAuthentication)
  518. return
  519. }
  520. var args DownloadZipArgs
  521. decodeErr := json.NewDecoder(r.Body).Decode(&args)
  522. if decodeErr != nil {
  523. writeWebErrorResponse(w, decodeErr)
  524. return
  525. }
  526. archive := zip.NewWriter(w)
  527. defer archive.Close()
  528. for _, object := range args.Objects {
  529. // Writes compressed object file to the response.
  530. zipit := func(objectName string) error {
  531. info, err := objectAPI.GetObjectInfo(args.BucketName, objectName)
  532. if err != nil {
  533. return err
  534. }
  535. header := &zip.FileHeader{
  536. Name: strings.TrimPrefix(objectName, args.Prefix),
  537. Method: zip.Deflate,
  538. UncompressedSize64: uint64(info.Size),
  539. UncompressedSize: uint32(info.Size),
  540. }
  541. writer, err := archive.CreateHeader(header)
  542. if err != nil {
  543. writeWebErrorResponse(w, errUnexpected)
  544. return err
  545. }
  546. return objectAPI.GetObject(args.BucketName, objectName, 0, info.Size, writer)
  547. }
  548. if !hasSuffix(object, slashSeparator) {
  549. // If not a directory, compress the file and write it to response.
  550. err := zipit(pathJoin(args.Prefix, object))
  551. if err != nil {
  552. return
  553. }
  554. continue
  555. }
  556. // For directories, list the contents recursively and write the objects as compressed
  557. // date to the response writer.
  558. marker := ""
  559. for {
  560. lo, err := objectAPI.ListObjects(args.BucketName, pathJoin(args.Prefix, object), marker, "", 1000)
  561. if err != nil {
  562. return
  563. }
  564. marker = lo.NextMarker
  565. for _, obj := range lo.Objects {
  566. err = zipit(obj.Name)
  567. if err != nil {
  568. return
  569. }
  570. }
  571. if !lo.IsTruncated {
  572. break
  573. }
  574. }
  575. }
  576. }
  577. // GetBucketPolicyArgs - get bucket policy args.
  578. type GetBucketPolicyArgs struct {
  579. BucketName string `json:"bucketName"`
  580. Prefix string `json:"prefix"`
  581. }
  582. // GetBucketPolicyRep - get bucket policy reply.
  583. type GetBucketPolicyRep struct {
  584. UIVersion string `json:"uiVersion"`
  585. Policy policy.BucketPolicy `json:"policy"`
  586. }
  587. func readBucketAccessPolicy(objAPI ObjectLayer, bucketName string) (policy.BucketAccessPolicy, error) {
  588. bucketPolicyReader, err := readBucketPolicyJSON(bucketName, objAPI)
  589. if err != nil {
  590. if _, ok := err.(BucketPolicyNotFound); ok {
  591. return policy.BucketAccessPolicy{Version: "2012-10-17"}, nil
  592. }
  593. return policy.BucketAccessPolicy{}, err
  594. }
  595. bucketPolicyBuf, err := ioutil.ReadAll(bucketPolicyReader)
  596. if err != nil {
  597. return policy.BucketAccessPolicy{}, err
  598. }
  599. policyInfo := policy.BucketAccessPolicy{}
  600. err = json.Unmarshal(bucketPolicyBuf, &policyInfo)
  601. if err != nil {
  602. return policy.BucketAccessPolicy{}, err
  603. }
  604. return policyInfo, nil
  605. }
  606. // GetBucketPolicy - get bucket policy.
  607. func (web *webAPIHandlers) GetBucketPolicy(r *http.Request, args *GetBucketPolicyArgs, reply *GetBucketPolicyRep) error {
  608. objectAPI := web.ObjectAPI()
  609. if objectAPI == nil {
  610. return toJSONError(errServerNotInitialized)
  611. }
  612. if !isHTTPRequestValid(r) {
  613. return toJSONError(errAuthentication)
  614. }
  615. policyInfo, err := readBucketAccessPolicy(objectAPI, args.BucketName)
  616. if err != nil {
  617. return toJSONError(err, args.BucketName)
  618. }
  619. reply.UIVersion = browser.UIVersion
  620. reply.Policy = policy.GetPolicy(policyInfo.Statements, args.BucketName, args.Prefix)
  621. return nil
  622. }
  623. // ListAllBucketPoliciesArgs - get all bucket policies.
  624. type ListAllBucketPoliciesArgs struct {
  625. BucketName string `json:"bucketName"`
  626. }
  627. // Collection of canned bucket policy at a given prefix.
  628. type bucketAccessPolicy struct {
  629. Prefix string `json:"prefix"`
  630. Policy policy.BucketPolicy `json:"policy"`
  631. }
  632. // ListAllBucketPoliciesRep - get all bucket policy reply.
  633. type ListAllBucketPoliciesRep struct {
  634. UIVersion string `json:"uiVersion"`
  635. Policies []bucketAccessPolicy `json:"policies"`
  636. }
  637. // GetllBucketPolicy - get all bucket policy.
  638. func (web *webAPIHandlers) ListAllBucketPolicies(r *http.Request, args *ListAllBucketPoliciesArgs, reply *ListAllBucketPoliciesRep) error {
  639. objectAPI := web.ObjectAPI()
  640. if objectAPI == nil {
  641. return toJSONError(errServerNotInitialized)
  642. }
  643. if !isHTTPRequestValid(r) {
  644. return toJSONError(errAuthentication)
  645. }
  646. policyInfo, err := readBucketAccessPolicy(objectAPI, args.BucketName)
  647. if err != nil {
  648. return toJSONError(err, args.BucketName)
  649. }
  650. reply.UIVersion = browser.UIVersion
  651. for prefix, policy := range policy.GetPolicies(policyInfo.Statements, args.BucketName) {
  652. reply.Policies = append(reply.Policies, bucketAccessPolicy{
  653. Prefix: prefix,
  654. Policy: policy,
  655. })
  656. }
  657. return nil
  658. }
  659. // SetBucketPolicyArgs - set bucket policy args.
  660. type SetBucketPolicyArgs struct {
  661. BucketName string `json:"bucketName"`
  662. Prefix string `json:"prefix"`
  663. Policy string `json:"policy"`
  664. }
  665. // SetBucketPolicy - set bucket policy.
  666. func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolicyArgs, reply *WebGenericRep) error {
  667. objectAPI := web.ObjectAPI()
  668. if objectAPI == nil {
  669. return toJSONError(errServerNotInitialized)
  670. }
  671. if !isHTTPRequestValid(r) {
  672. return toJSONError(errAuthentication)
  673. }
  674. bucketP := policy.BucketPolicy(args.Policy)
  675. if !bucketP.IsValidBucketPolicy() {
  676. return &json2.Error{
  677. Message: "Invalid policy type " + args.Policy,
  678. }
  679. }
  680. policyInfo, err := readBucketAccessPolicy(objectAPI, args.BucketName)
  681. if err != nil {
  682. return toJSONError(err, args.BucketName)
  683. }
  684. policyInfo.Statements = policy.SetPolicy(policyInfo.Statements, bucketP, args.BucketName, args.Prefix)
  685. if len(policyInfo.Statements) == 0 {
  686. err = persistAndNotifyBucketPolicyChange(args.BucketName, policyChange{true, nil}, objectAPI)
  687. if err != nil {
  688. return toJSONError(err, args.BucketName)
  689. }
  690. reply.UIVersion = browser.UIVersion
  691. return nil
  692. }
  693. data, err := json.Marshal(policyInfo)
  694. if err != nil {
  695. return toJSONError(err)
  696. }
  697. // Parse validate and save bucket policy.
  698. if s3Error := parseAndPersistBucketPolicy(args.BucketName, data, objectAPI); s3Error != ErrNone {
  699. apiErr := getAPIError(s3Error)
  700. var err error
  701. if apiErr.Code == "XMinioPolicyNesting" {
  702. err = PolicyNesting{}
  703. } else {
  704. err = errors.New(apiErr.Description)
  705. }
  706. return toJSONError(err, args.BucketName)
  707. }
  708. reply.UIVersion = browser.UIVersion
  709. return nil
  710. }
  711. // PresignedGetArgs - presigned-get API args.
  712. type PresignedGetArgs struct {
  713. // Host header required for signed headers.
  714. HostName string `json:"host"`
  715. // Bucket name of the object to be presigned.
  716. BucketName string `json:"bucket"`
  717. // Object name to be presigned.
  718. ObjectName string `json:"object"`
  719. // Expiry in seconds.
  720. Expiry int64 `json:"expiry"`
  721. }
  722. // PresignedGetRep - presigned-get URL reply.
  723. type PresignedGetRep struct {
  724. UIVersion string `json:"uiVersion"`
  725. // Presigned URL of the object.
  726. URL string `json:"url"`
  727. }
  728. // PresignedGET - returns presigned-Get url.
  729. func (web *webAPIHandlers) PresignedGet(r *http.Request, args *PresignedGetArgs, reply *PresignedGetRep) error {
  730. if !isHTTPRequestValid(r) {
  731. return toJSONError(errAuthentication)
  732. }
  733. if args.BucketName == "" || args.ObjectName == "" {
  734. return &json2.Error{
  735. Message: "Bucket and Object are mandatory arguments.",
  736. }
  737. }
  738. reply.UIVersion = browser.UIVersion
  739. reply.URL = presignedGet(args.HostName, args.BucketName, args.ObjectName, args.Expiry)
  740. return nil
  741. }
  742. // Returns presigned url for GET method.
  743. func presignedGet(host, bucket, object string, expiry int64) string {
  744. cred := serverConfig.GetCredential()
  745. region := serverConfig.GetRegion()
  746. accessKey := cred.AccessKey
  747. secretKey := cred.SecretKey
  748. date := time.Now().UTC()
  749. dateStr := date.Format(iso8601Format)
  750. credential := fmt.Sprintf("%s/%s", accessKey, getScope(date, region))
  751. var expiryStr = "604800" // Default set to be expire in 7days.
  752. if expiry < 604800 && expiry > 0 {
  753. expiryStr = strconv.FormatInt(expiry, 10)
  754. }
  755. query := strings.Join([]string{
  756. "X-Amz-Algorithm=" + signV4Algorithm,
  757. "X-Amz-Credential=" + strings.Replace(credential, "/", "%2F", -1),
  758. "X-Amz-Date=" + dateStr,
  759. "X-Amz-Expires=" + expiryStr,
  760. "X-Amz-SignedHeaders=host",
  761. }, "&")
  762. path := "/" + path.Join(bucket, object)
  763. // Headers are empty, since "host" is the only header required to be signed for Presigned URLs.
  764. var extractedSignedHeaders http.Header
  765. canonicalRequest := getCanonicalRequest(extractedSignedHeaders, unsignedPayload, query, path, "GET", host)
  766. stringToSign := getStringToSign(canonicalRequest, date, getScope(date, region))
  767. signingKey := getSigningKey(secretKey, date, region)
  768. signature := getSignature(signingKey, stringToSign)
  769. // Construct the final presigned URL.
  770. return host + path + "?" + query + "&" + "X-Amz-Signature=" + signature
  771. }
  772. // toJSONError converts regular errors into more user friendly
  773. // and consumable error message for the browser UI.
  774. func toJSONError(err error, params ...string) (jerr *json2.Error) {
  775. apiErr := toWebAPIError(err)
  776. jerr = &json2.Error{
  777. Message: apiErr.Description,
  778. }
  779. switch apiErr.Code {
  780. // Reserved bucket name provided.
  781. case "AllAccessDisabled":
  782. if len(params) > 0 {
  783. jerr = &json2.Error{
  784. Message: fmt.Sprintf("All access to this bucket %s has been disabled.", params[0]),
  785. }
  786. }
  787. // Bucket name invalid with custom error message.
  788. case "InvalidBucketName":
  789. if len(params) > 0 {
  790. jerr = &json2.Error{
  791. Message: fmt.Sprintf("Bucket Name %s is invalid. Lowercase letters, period, numerals are the only allowed characters and should be minimum 3 characters in length.", params[0]),
  792. }
  793. }
  794. // Bucket not found custom error message.
  795. case "NoSuchBucket":
  796. if len(params) > 0 {
  797. jerr = &json2.Error{
  798. Message: fmt.Sprintf("The specified bucket %s does not exist.", params[0]),
  799. }
  800. }
  801. // Object not found custom error message.
  802. case "NoSuchKey":
  803. if len(params) > 1 {
  804. jerr = &json2.Error{
  805. Message: fmt.Sprintf("The specified key %s does not exist", params[1]),
  806. }
  807. }
  808. // Add more custom error messages here with more context.
  809. }
  810. return jerr
  811. }
  812. // toWebAPIError - convert into error into APIError.
  813. func toWebAPIError(err error) APIError {
  814. err = errorCause(err)
  815. if err == errAuthentication {
  816. return APIError{
  817. Code: "AccessDenied",
  818. HTTPStatusCode: http.StatusForbidden,
  819. Description: err.Error(),
  820. }
  821. } else if err == errServerNotInitialized {
  822. return APIError{
  823. Code: "XMinioServerNotInitialized",
  824. HTTPStatusCode: http.StatusServiceUnavailable,
  825. Description: err.Error(),
  826. }
  827. } else if err == errInvalidAccessKeyLength {
  828. return APIError{
  829. Code: "AccessDenied",
  830. HTTPStatusCode: http.StatusForbidden,
  831. Description: err.Error(),
  832. }
  833. } else if err == errInvalidSecretKeyLength {
  834. return APIError{
  835. Code: "AccessDenied",
  836. HTTPStatusCode: http.StatusForbidden,
  837. Description: err.Error(),
  838. }
  839. } else if err == errInvalidAccessKeyID {
  840. return APIError{
  841. Code: "AccessDenied",
  842. HTTPStatusCode: http.StatusForbidden,
  843. Description: err.Error(),
  844. }
  845. } else if err == errSizeUnspecified {
  846. return APIError{
  847. Code: "InvalidRequest",
  848. HTTPStatusCode: http.StatusBadRequest,
  849. Description: err.Error(),
  850. }
  851. } else if err == errChangeCredNotAllowed {
  852. return APIError{
  853. Code: "MethodNotAllowed",
  854. HTTPStatusCode: http.StatusMethodNotAllowed,
  855. Description: err.Error(),
  856. }
  857. } else if err == errReservedBucket {
  858. return APIError{
  859. Code: "AllAccessDisabled",
  860. HTTPStatusCode: http.StatusForbidden,
  861. Description: err.Error(),
  862. }
  863. }
  864. // Convert error type to api error code.
  865. var apiErrCode APIErrorCode
  866. switch err.(type) {
  867. case StorageFull:
  868. apiErrCode = ErrStorageFull
  869. case BucketNotFound:
  870. apiErrCode = ErrNoSuchBucket
  871. case BucketExists:
  872. apiErrCode = ErrBucketAlreadyOwnedByYou
  873. case BucketNameInvalid:
  874. apiErrCode = ErrInvalidBucketName
  875. case BadDigest:
  876. apiErrCode = ErrBadDigest
  877. case IncompleteBody:
  878. apiErrCode = ErrIncompleteBody
  879. case ObjectExistsAsDirectory:
  880. apiErrCode = ErrObjectExistsAsDirectory
  881. case ObjectNotFound:
  882. apiErrCode = ErrNoSuchKey
  883. case ObjectNameInvalid:
  884. apiErrCode = ErrNoSuchKey
  885. case InsufficientWriteQuorum:
  886. apiErrCode = ErrWriteQuorum
  887. case InsufficientReadQuorum:
  888. apiErrCode = ErrReadQuorum
  889. case PolicyNesting:
  890. apiErrCode = ErrPolicyNesting
  891. default:
  892. // Log unexpected and unhandled errors.
  893. errorIf(err, errUnexpected.Error())
  894. apiErrCode = ErrInternalError
  895. }
  896. apiErr := getAPIError(apiErrCode)
  897. return apiErr
  898. }
  899. // writeWebErrorResponse - set HTTP status code and write error description to the body.
  900. func writeWebErrorResponse(w http.ResponseWriter, err error) {
  901. apiErr := toWebAPIError(err)
  902. w.WriteHeader(apiErr.HTTPStatusCode)
  903. w.Write([]byte(apiErr.Description))
  904. }