s3api_object_handlers_acl.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. package s3api
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "net/http"
  7. "github.com/seaweedfs/seaweedfs/weed/glog"
  8. "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
  9. "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
  10. "github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
  11. )
  12. // GetObjectAclHandler Get object ACL
  13. // https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectAcl.html
  14. func (s3a *S3ApiServer) GetObjectAclHandler(w http.ResponseWriter, r *http.Request) {
  15. // collect parameters
  16. bucket, object := s3_constants.GetBucketAndObject(r)
  17. glog.V(3).Infof("GetObjectAclHandler %s %s", bucket, object)
  18. if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
  19. s3err.WriteErrorResponse(w, r, err)
  20. return
  21. }
  22. // Check for specific version ID in query parameters
  23. versionId := r.URL.Query().Get("versionId")
  24. // Check if versioning is configured for the bucket (Enabled or Suspended)
  25. versioningConfigured, err := s3a.isVersioningConfigured(bucket)
  26. if err != nil {
  27. if err == filer_pb.ErrNotFound {
  28. s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket)
  29. return
  30. }
  31. glog.Errorf("GetObjectAclHandler: Error checking versioning status for bucket %s: %v", bucket, err)
  32. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  33. return
  34. }
  35. var entry *filer_pb.Entry
  36. if versioningConfigured {
  37. // Handle versioned object ACL retrieval - use same logic as GetObjectHandler
  38. if versionId != "" {
  39. // Request for specific version
  40. glog.V(2).Infof("GetObjectAclHandler: requesting ACL for specific version %s of %s%s", versionId, bucket, object)
  41. entry, err = s3a.getSpecificObjectVersion(bucket, object, versionId)
  42. } else {
  43. // Request for latest version
  44. glog.V(2).Infof("GetObjectAclHandler: requesting ACL for latest version of %s%s", bucket, object)
  45. entry, err = s3a.getLatestObjectVersion(bucket, object)
  46. }
  47. if err != nil {
  48. glog.Errorf("GetObjectAclHandler: Failed to get object version %s for %s%s: %v", versionId, bucket, object, err)
  49. s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey)
  50. return
  51. }
  52. // Check if this is a delete marker
  53. if entry.Extended != nil {
  54. if deleteMarker, exists := entry.Extended[s3_constants.ExtDeleteMarkerKey]; exists && string(deleteMarker) == "true" {
  55. s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey)
  56. return
  57. }
  58. }
  59. } else {
  60. // Handle regular (non-versioned) object ACL retrieval
  61. bucketDir := s3a.option.BucketsPath + "/" + bucket
  62. entry, err = s3a.getEntry(bucketDir, object)
  63. if err != nil {
  64. if errors.Is(err, filer_pb.ErrNotFound) {
  65. s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey)
  66. return
  67. }
  68. glog.Errorf("GetObjectAclHandler: error checking object %s/%s: %v", bucket, object, err)
  69. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  70. return
  71. }
  72. }
  73. if entry == nil {
  74. s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey)
  75. return
  76. }
  77. // Get object owner from metadata, fallback to request account
  78. var objectOwner string
  79. var objectOwnerDisplayName string
  80. amzAccountId := r.Header.Get(s3_constants.AmzAccountId)
  81. if entry.Extended != nil {
  82. if ownerBytes, exists := entry.Extended[s3_constants.ExtAmzOwnerKey]; exists {
  83. objectOwner = string(ownerBytes)
  84. }
  85. }
  86. // Fallback to current account if no owner stored
  87. if objectOwner == "" {
  88. objectOwner = amzAccountId
  89. }
  90. objectOwnerDisplayName = s3a.iam.GetAccountNameById(objectOwner)
  91. // Build ACL response
  92. response := AccessControlPolicy{
  93. Owner: CanonicalUser{
  94. ID: objectOwner,
  95. DisplayName: objectOwnerDisplayName,
  96. },
  97. }
  98. // Get grants from stored ACL metadata
  99. grants := GetAcpGrants(entry.Extended)
  100. if len(grants) > 0 {
  101. // Convert AWS SDK grants to local Grant format
  102. for _, grant := range grants {
  103. localGrant := Grant{
  104. Permission: Permission(*grant.Permission),
  105. }
  106. if grant.Grantee != nil {
  107. localGrant.Grantee = Grantee{
  108. Type: *grant.Grantee.Type,
  109. XMLXSI: "CanonicalUser",
  110. XMLNS: "http://www.w3.org/2001/XMLSchema-instance",
  111. }
  112. if grant.Grantee.ID != nil {
  113. localGrant.Grantee.ID = *grant.Grantee.ID
  114. localGrant.Grantee.DisplayName = s3a.iam.GetAccountNameById(*grant.Grantee.ID)
  115. }
  116. if grant.Grantee.URI != nil {
  117. localGrant.Grantee.URI = *grant.Grantee.URI
  118. }
  119. }
  120. response.AccessControlList.Grant = append(response.AccessControlList.Grant, localGrant)
  121. }
  122. } else {
  123. // Fallback to default full control for object owner
  124. response.AccessControlList.Grant = append(response.AccessControlList.Grant, Grant{
  125. Grantee: Grantee{
  126. ID: objectOwner,
  127. DisplayName: objectOwnerDisplayName,
  128. Type: "CanonicalUser",
  129. XMLXSI: "CanonicalUser",
  130. XMLNS: "http://www.w3.org/2001/XMLSchema-instance"},
  131. Permission: Permission(s3_constants.PermissionFullControl),
  132. })
  133. }
  134. writeSuccessResponseXML(w, r, response)
  135. }
  136. // PutObjectAclHandler Put object ACL
  137. // https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectAcl.html
  138. func (s3a *S3ApiServer) PutObjectAclHandler(w http.ResponseWriter, r *http.Request) {
  139. // collect parameters
  140. bucket, object := s3_constants.GetBucketAndObject(r)
  141. glog.V(3).Infof("PutObjectAclHandler %s %s", bucket, object)
  142. if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
  143. s3err.WriteErrorResponse(w, r, err)
  144. return
  145. }
  146. // Check for specific version ID in query parameters
  147. versionId := r.URL.Query().Get("versionId")
  148. // Check if versioning is configured for the bucket (Enabled or Suspended)
  149. versioningConfigured, err := s3a.isVersioningConfigured(bucket)
  150. if err != nil {
  151. if err == filer_pb.ErrNotFound {
  152. s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket)
  153. return
  154. }
  155. glog.Errorf("PutObjectAclHandler: Error checking versioning status for bucket %s: %v", bucket, err)
  156. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  157. return
  158. }
  159. var entry *filer_pb.Entry
  160. if versioningConfigured {
  161. // Handle versioned object ACL modification - use same logic as GetObjectHandler
  162. if versionId != "" {
  163. // Request for specific version
  164. glog.V(2).Infof("PutObjectAclHandler: modifying ACL for specific version %s of %s%s", versionId, bucket, object)
  165. entry, err = s3a.getSpecificObjectVersion(bucket, object, versionId)
  166. } else {
  167. // Request for latest version
  168. glog.V(2).Infof("PutObjectAclHandler: modifying ACL for latest version of %s%s", bucket, object)
  169. entry, err = s3a.getLatestObjectVersion(bucket, object)
  170. }
  171. if err != nil {
  172. glog.Errorf("PutObjectAclHandler: Failed to get object version %s for %s%s: %v", versionId, bucket, object, err)
  173. s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey)
  174. return
  175. }
  176. // Check if this is a delete marker
  177. if entry.Extended != nil {
  178. if deleteMarker, exists := entry.Extended[s3_constants.ExtDeleteMarkerKey]; exists && string(deleteMarker) == "true" {
  179. s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey)
  180. return
  181. }
  182. }
  183. } else {
  184. // Handle regular (non-versioned) object ACL modification
  185. bucketDir := s3a.option.BucketsPath + "/" + bucket
  186. entry, err = s3a.getEntry(bucketDir, object)
  187. if err != nil {
  188. if errors.Is(err, filer_pb.ErrNotFound) {
  189. s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey)
  190. return
  191. }
  192. glog.Errorf("PutObjectAclHandler: error checking object %s/%s: %v", bucket, object, err)
  193. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  194. return
  195. }
  196. }
  197. if entry == nil {
  198. s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey)
  199. return
  200. }
  201. // Get current object owner from metadata
  202. var objectOwner string
  203. amzAccountId := r.Header.Get(s3_constants.AmzAccountId)
  204. if entry.Extended != nil {
  205. if ownerBytes, exists := entry.Extended[s3_constants.ExtAmzOwnerKey]; exists {
  206. objectOwner = string(ownerBytes)
  207. }
  208. }
  209. // Fallback to current account if no owner stored
  210. if objectOwner == "" {
  211. objectOwner = amzAccountId
  212. }
  213. // **PERMISSION CHECKS**
  214. // 1. Check if user is admin (admins can modify any ACL)
  215. if !s3a.isUserAdmin(r) {
  216. // 2. Check object ownership - only object owner can modify ACL (unless admin)
  217. if objectOwner != amzAccountId {
  218. glog.V(3).Infof("PutObjectAclHandler: Access denied - user %s is not owner of object %s/%s (owner: %s)",
  219. amzAccountId, bucket, object, objectOwner)
  220. s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied)
  221. return
  222. }
  223. // 3. Check object-level WRITE_ACP permission
  224. // Create the specific action for this object
  225. writeAcpAction := Action(fmt.Sprintf("WriteAcp:%s/%s", bucket, object))
  226. identity, errCode := s3a.iam.authRequest(r, writeAcpAction)
  227. if errCode != s3err.ErrNone {
  228. glog.V(3).Infof("PutObjectAclHandler: Auth failed for WriteAcp action on %s/%s: %v", bucket, object, errCode)
  229. s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied)
  230. return
  231. }
  232. // 4. Verify the authenticated identity can perform WriteAcp on this specific object
  233. if identity == nil || !identity.canDo(writeAcpAction, bucket, object) {
  234. glog.V(3).Infof("PutObjectAclHandler: Identity %v cannot perform WriteAcp on %s/%s", identity, bucket, object)
  235. s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied)
  236. return
  237. }
  238. } else {
  239. glog.V(3).Infof("PutObjectAclHandler: Admin user %s granted ACL modification permission for %s/%s", amzAccountId, bucket, object)
  240. }
  241. // Get bucket config for ownership settings
  242. bucketConfig, errCode := s3a.getBucketConfig(bucket)
  243. if errCode != s3err.ErrNone {
  244. s3err.WriteErrorResponse(w, r, errCode)
  245. return
  246. }
  247. bucketOwnership := bucketConfig.Ownership
  248. bucketOwnerId := bucketConfig.Owner
  249. // Extract ACL from request (either canned ACL or XML body)
  250. // This function also validates that the owner in the request matches the object owner
  251. grants, errCode := ExtractAcl(r, s3a.iam, bucketOwnership, bucketOwnerId, objectOwner, amzAccountId)
  252. if errCode != s3err.ErrNone {
  253. s3err.WriteErrorResponse(w, r, errCode)
  254. return
  255. }
  256. // Store ACL in object metadata
  257. if errCode := AssembleEntryWithAcp(entry, objectOwner, grants); errCode != s3err.ErrNone {
  258. glog.Errorf("PutObjectAclHandler: failed to assemble entry with ACP: %v", errCode)
  259. s3err.WriteErrorResponse(w, r, errCode)
  260. return
  261. }
  262. // Calculate the correct directory for ACL update
  263. var updateDirectory string
  264. if versioningConfigured {
  265. if versionId != "" && versionId != "null" {
  266. // Versioned object - update the specific version file in .versions directory
  267. updateDirectory = s3a.option.BucketsPath + "/" + bucket + "/" + object + ".versions"
  268. } else {
  269. // Latest version in versioned bucket - could be null version or versioned object
  270. // Extract version ID from the entry to determine where it's stored
  271. var actualVersionId string
  272. if entry.Extended != nil {
  273. if versionIdBytes, exists := entry.Extended[s3_constants.ExtVersionIdKey]; exists {
  274. actualVersionId = string(versionIdBytes)
  275. }
  276. }
  277. if actualVersionId == "null" || actualVersionId == "" {
  278. // Null version (pre-versioning object) - stored as regular file
  279. updateDirectory = s3a.option.BucketsPath + "/" + bucket
  280. } else {
  281. // Versioned object - stored in .versions directory
  282. updateDirectory = s3a.option.BucketsPath + "/" + bucket + "/" + object + ".versions"
  283. }
  284. }
  285. } else {
  286. // Non-versioned object - stored as regular file
  287. updateDirectory = s3a.option.BucketsPath + "/" + bucket
  288. }
  289. // Update the object with new ACL metadata
  290. err = s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
  291. request := &filer_pb.UpdateEntryRequest{
  292. Directory: updateDirectory,
  293. Entry: entry,
  294. }
  295. if _, err := client.UpdateEntry(context.Background(), request); err != nil {
  296. return err
  297. }
  298. return nil
  299. })
  300. if err != nil {
  301. glog.Errorf("PutObjectAclHandler: failed to update entry: %v", err)
  302. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  303. return
  304. }
  305. glog.V(3).Infof("PutObjectAclHandler: Successfully updated ACL for %s/%s by user %s", bucket, object, amzAccountId)
  306. writeSuccessResponseEmpty(w, r)
  307. }