// Copyright (c) 2015-2021 MinIO, Inc. // // This file is part of MinIO Object Storage stack // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package cmd import ( "encoding/xml" "io" "net/http" "github.com/gorilla/mux" xhttp "github.com/minio/minio/internal/http" "github.com/minio/minio/internal/logger" "github.com/minio/pkg/bucket/policy" ) // Data types used for returning dummy access control // policy XML, these variables shouldn't be used elsewhere // they are only defined to be used in this file alone. type grantee struct { XMLNS string `xml:"xmlns:xsi,attr"` XMLXSI string `xml:"xsi:type,attr"` Type string `xml:"Type"` ID string `xml:"ID,omitempty"` DisplayName string `xml:"DisplayName,omitempty"` URI string `xml:"URI,omitempty"` } type grant struct { Grantee grantee `xml:"Grantee"` Permission string `xml:"Permission"` } type accessControlPolicy struct { XMLName xml.Name `xml:"AccessControlPolicy"` Owner Owner `xml:"Owner"` AccessControlList struct { Grants []grant `xml:"Grant"` } `xml:"AccessControlList"` } // PutBucketACLHandler - PUT Bucket ACL // ----------------- // This operation uses the ACL subresource // to set ACL for a bucket, this is a dummy call // only responds success if the ACL is private. func (api objectAPIHandlers) PutBucketACLHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "PutBucketACL") defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) vars := mux.Vars(r) bucket := vars["bucket"] objAPI := api.ObjectAPI() if objAPI == nil { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) return } // Allow putBucketACL if policy action is set, since this is a dummy call // we are simply re-purposing the bucketPolicyAction. if s3Error := checkRequestAuthType(ctx, r, policy.PutBucketPolicyAction, bucket, ""); s3Error != ErrNone { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) return } // Before proceeding validate if bucket exists. _, err := objAPI.GetBucketInfo(ctx, bucket) if err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) return } aclHeader := r.Header.Get(xhttp.AmzACL) if aclHeader == "" { acl := &accessControlPolicy{} if err = xmlDecoder(r.Body, acl, r.ContentLength); err != nil { if err == io.EOF { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingSecurityHeader), r.URL) return } writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) return } if len(acl.AccessControlList.Grants) == 0 { writeErrorResponse(ctx, w, toAPIError(ctx, NotImplemented{}), r.URL) return } if acl.AccessControlList.Grants[0].Permission != "FULL_CONTROL" { writeErrorResponse(ctx, w, toAPIError(ctx, NotImplemented{}), r.URL) return } } if aclHeader != "" && aclHeader != "private" { writeErrorResponse(ctx, w, toAPIError(ctx, NotImplemented{}), r.URL) return } w.(http.Flusher).Flush() } // GetBucketACLHandler - GET Bucket ACL // ----------------- // This operation uses the ACL // subresource to return the ACL of a specified bucket. func (api objectAPIHandlers) GetBucketACLHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "GetBucketACL") defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) vars := mux.Vars(r) bucket := vars["bucket"] objAPI := api.ObjectAPI() if objAPI == nil { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) return } // Allow getBucketACL if policy action is set, since this is a dummy call // we are simply re-purposing the bucketPolicyAction. if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketPolicyAction, bucket, ""); s3Error != ErrNone { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) return } // Before proceeding validate if bucket exists. _, err := objAPI.GetBucketInfo(ctx, bucket) if err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) return } acl := &accessControlPolicy{} acl.AccessControlList.Grants = append(acl.AccessControlList.Grants, grant{ Grantee: grantee{ XMLNS: "http://www.w3.org/2001/XMLSchema-instance", XMLXSI: "CanonicalUser", Type: "CanonicalUser", }, Permission: "FULL_CONTROL", }) if err := xml.NewEncoder(w).Encode(acl); err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) return } w.(http.Flusher).Flush() } // PutObjectACLHandler - PUT Object ACL // ----------------- // This operation uses the ACL subresource // to set ACL for a bucket, this is a dummy call // only responds success if the ACL is private. func (api objectAPIHandlers) PutObjectACLHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "PutObjectACL") defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) vars := mux.Vars(r) bucket := vars["bucket"] object, err := unescapePath(vars["object"]) if err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) return } objAPI := api.ObjectAPI() if objAPI == nil { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) return } // Allow putObjectACL if policy action is set, since this is a dummy call // we are simply re-purposing the bucketPolicyAction. if s3Error := checkRequestAuthType(ctx, r, policy.PutBucketPolicyAction, bucket, ""); s3Error != ErrNone { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) return } // Before proceeding validate if object exists. _, err = objAPI.GetObjectInfo(ctx, bucket, object, ObjectOptions{}) if err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) return } aclHeader := r.Header.Get(xhttp.AmzACL) if aclHeader == "" { acl := &accessControlPolicy{} if err = xmlDecoder(r.Body, acl, r.ContentLength); err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) return } if len(acl.AccessControlList.Grants) == 0 { writeErrorResponse(ctx, w, toAPIError(ctx, NotImplemented{}), r.URL) return } if acl.AccessControlList.Grants[0].Permission != "FULL_CONTROL" { writeErrorResponse(ctx, w, toAPIError(ctx, NotImplemented{}), r.URL) return } } if aclHeader != "" && aclHeader != "private" { writeErrorResponse(ctx, w, toAPIError(ctx, NotImplemented{}), r.URL) return } w.(http.Flusher).Flush() } // GetObjectACLHandler - GET Object ACL // ----------------- // This operation uses the ACL // subresource to return the ACL of a specified object. func (api objectAPIHandlers) GetObjectACLHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "GetObjectACL") defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) vars := mux.Vars(r) bucket := vars["bucket"] object, err := unescapePath(vars["object"]) if err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) return } objAPI := api.ObjectAPI() if objAPI == nil { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) return } // Allow getObjectACL if policy action is set, since this is a dummy call // we are simply re-purposing the bucketPolicyAction. if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketPolicyAction, bucket, ""); s3Error != ErrNone { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) return } // Before proceeding validate if object exists. _, err = objAPI.GetObjectInfo(ctx, bucket, object, ObjectOptions{}) if err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) return } acl := &accessControlPolicy{} acl.AccessControlList.Grants = append(acl.AccessControlList.Grants, grant{ Grantee: grantee{ XMLNS: "http://www.w3.org/2001/XMLSchema-instance", XMLXSI: "CanonicalUser", Type: "CanonicalUser", }, Permission: "FULL_CONTROL", }) if err := xml.NewEncoder(w).Encode(acl); err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) return } w.(http.Flusher).Flush() }