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.
		
		
		
		
		
			
		
			
				
					
					
						
							513 lines
						
					
					
						
							16 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							513 lines
						
					
					
						
							16 KiB
						
					
					
				| package s3api | |
| 
 | |
| import ( | |
| 	"encoding/json" | |
| 	"encoding/xml" | |
| 	"github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil" | |
| 	"github.com/aws/aws-sdk-go/service/s3" | |
| 	"github.com/seaweedfs/seaweedfs/weed/glog" | |
| 	"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" | |
| 	"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" | |
| 	"github.com/seaweedfs/seaweedfs/weed/s3api/s3err" | |
| 	util_http "github.com/seaweedfs/seaweedfs/weed/util/http" | |
| 	"net/http" | |
| 	"strings" | |
| ) | |
| 
 | |
| type AccountManager interface { | |
| 	GetAccountNameById(canonicalId string) string | |
| 	GetAccountIdByEmail(email string) string | |
| } | |
| 
 | |
| // GetAccountId get AccountId from request headers, AccountAnonymousId will be return if not presen | |
| func GetAccountId(r *http.Request) string { | |
| 	id := r.Header.Get(s3_constants.AmzAccountId) | |
| 	if len(id) == 0 { | |
| 		return s3_constants.AccountAnonymousId | |
| 	} else { | |
| 		return id | |
| 	} | |
| } | |
| 
 | |
| // ExtractAcl extracts the acl from the request body, or from the header if request body is empty | |
| func ExtractAcl(r *http.Request, accountManager AccountManager, ownership, bucketOwnerId, ownerId, accountId string) (grants []*s3.Grant, errCode s3err.ErrorCode) { | |
| 	if r.Body != nil && r.Body != http.NoBody { | |
| 		defer util_http.CloseRequest(r) | |
| 
 | |
| 		var acp s3.AccessControlPolicy | |
| 		err := xmlutil.UnmarshalXML(&acp, xml.NewDecoder(r.Body), "") | |
| 		if err != nil || acp.Owner == nil || acp.Owner.ID == nil { | |
| 			return nil, s3err.ErrInvalidRequest | |
| 		} | |
| 
 | |
| 		//owner should present && owner is immutable | |
| 		if *acp.Owner.ID != ownerId { | |
| 			glog.V(3).Infof("set acl denied! owner account is not consistent, request account id: %s, expect account id: %s", accountId, ownerId) | |
| 			return nil, s3err.ErrAccessDenied | |
| 		} | |
| 
 | |
| 		return ValidateAndTransferGrants(accountManager, acp.Grants) | |
| 	} else { | |
| 		_, grants, errCode = ParseAndValidateAclHeadersOrElseDefault(r, accountManager, ownership, bucketOwnerId, accountId, true) | |
| 		return grants, errCode | |
| 	} | |
| } | |
| 
 | |
| // ParseAndValidateAclHeadersOrElseDefault will callParseAndValidateAclHeaders to get Grants, if empty, it will return Grant that grant `accountId` with `FullControl` permission | |
| func ParseAndValidateAclHeadersOrElseDefault(r *http.Request, accountManager AccountManager, ownership, bucketOwnerId, accountId string, putAcl bool) (ownerId string, grants []*s3.Grant, errCode s3err.ErrorCode) { | |
| 	ownerId, grants, errCode = ParseAndValidateAclHeaders(r, accountManager, ownership, bucketOwnerId, accountId, putAcl) | |
| 	if errCode != s3err.ErrNone { | |
| 		return | |
| 	} | |
| 	if len(grants) == 0 { | |
| 		//if no acl(both customAcl and cannedAcl) specified, grant accountId(object writer) with full control permission | |
| 		grants = append(grants, &s3.Grant{ | |
| 			Grantee: &s3.Grantee{ | |
| 				Type: &s3_constants.GrantTypeCanonicalUser, | |
| 				ID:   &accountId, | |
| 			}, | |
| 			Permission: &s3_constants.PermissionFullControl, | |
| 		}) | |
| 	} | |
| 	return | |
| } | |
| 
 | |
| // ParseAndValidateAclHeaders parse and validate acl from header | |
| func ParseAndValidateAclHeaders(r *http.Request, accountManager AccountManager, ownership, bucketOwnerId, accountId string, putAcl bool) (ownerId string, grants []*s3.Grant, errCode s3err.ErrorCode) { | |
| 	ownerId, grants, errCode = ParseAclHeaders(r, ownership, bucketOwnerId, accountId, putAcl) | |
| 	if errCode != s3err.ErrNone { | |
| 		return | |
| 	} | |
| 	if len(grants) > 0 { | |
| 		grants, errCode = ValidateAndTransferGrants(accountManager, grants) | |
| 	} | |
| 	return | |
| } | |
| 
 | |
| // ParseAclHeaders parse acl headers | |
| // When `putAcl` is true, only `CannedAcl` is parsed, such as `PutBucketAcl` or `PutObjectAcl` | |
| // is requested, `CustomAcl` is parsed from the request body not from headers, and only if the | |
| // request body is empty, `CannedAcl` is parsed from the header, and will not parse `CustomAcl` from the header | |
| // | |
| // Since `CustomAcl` has higher priority, it will be parsed first; if `CustomAcl` does not exist, `CannedAcl` will be parsed | |
| func ParseAclHeaders(r *http.Request, ownership, bucketOwnerId, accountId string, putAcl bool) (ownerId string, grants []*s3.Grant, errCode s3err.ErrorCode) { | |
| 	if !putAcl { | |
| 		errCode = ParseCustomAclHeaders(r, &grants) | |
| 		if errCode != s3err.ErrNone { | |
| 			return "", nil, errCode | |
| 		} | |
| 	} | |
| 	if len(grants) > 0 { | |
| 		return accountId, grants, s3err.ErrNone | |
| 	} | |
| 
 | |
| 	cannedAcl := r.Header.Get(s3_constants.AmzCannedAcl) | |
| 	if len(cannedAcl) == 0 { | |
| 		return accountId, grants, s3err.ErrNone | |
| 	} | |
| 
 | |
| 	//if canned acl specified, parse cannedAcl (lower priority to custom acl) | |
| 	ownerId, grants, errCode = ParseCannedAclHeader(ownership, bucketOwnerId, accountId, cannedAcl, putAcl) | |
| 	if errCode != s3err.ErrNone { | |
| 		return "", nil, errCode | |
| 	} | |
| 	return ownerId, grants, errCode | |
| } | |
| 
 | |
| func ParseCustomAclHeaders(r *http.Request, grants *[]*s3.Grant) s3err.ErrorCode { | |
| 	customAclHeaders := []string{s3_constants.AmzAclFullControl, s3_constants.AmzAclRead, s3_constants.AmzAclReadAcp, s3_constants.AmzAclWrite, s3_constants.AmzAclWriteAcp} | |
| 	var errCode s3err.ErrorCode | |
| 	for _, customAclHeader := range customAclHeaders { | |
| 		headerValue := r.Header.Get(customAclHeader) | |
| 		switch customAclHeader { | |
| 		case s3_constants.AmzAclRead: | |
| 			errCode = ParseCustomAclHeader(headerValue, s3_constants.PermissionRead, grants) | |
| 		case s3_constants.AmzAclWrite: | |
| 			errCode = ParseCustomAclHeader(headerValue, s3_constants.PermissionWrite, grants) | |
| 		case s3_constants.AmzAclReadAcp: | |
| 			errCode = ParseCustomAclHeader(headerValue, s3_constants.PermissionReadAcp, grants) | |
| 		case s3_constants.AmzAclWriteAcp: | |
| 			errCode = ParseCustomAclHeader(headerValue, s3_constants.PermissionWriteAcp, grants) | |
| 		case s3_constants.AmzAclFullControl: | |
| 			errCode = ParseCustomAclHeader(headerValue, s3_constants.PermissionFullControl, grants) | |
| 		} | |
| 		if errCode != s3err.ErrNone { | |
| 			return errCode | |
| 		} | |
| 	} | |
| 	return s3err.ErrNone | |
| } | |
| 
 | |
| func ParseCustomAclHeader(headerValue, permission string, grants *[]*s3.Grant) s3err.ErrorCode { | |
| 	if len(headerValue) > 0 { | |
| 		split := strings.Split(headerValue, ", ") | |
| 		for _, grantStr := range split { | |
| 			kv := strings.Split(grantStr, "=") | |
| 			if len(kv) != 2 { | |
| 				return s3err.ErrInvalidRequest | |
| 			} | |
| 
 | |
| 			switch kv[0] { | |
| 			case "id": | |
| 				var accountId string | |
| 				_ = json.Unmarshal([]byte(kv[1]), &accountId) | |
| 				*grants = append(*grants, &s3.Grant{ | |
| 					Grantee: &s3.Grantee{ | |
| 						Type: &s3_constants.GrantTypeCanonicalUser, | |
| 						ID:   &accountId, | |
| 					}, | |
| 					Permission: &permission, | |
| 				}) | |
| 			case "emailAddress": | |
| 				var emailAddress string | |
| 				_ = json.Unmarshal([]byte(kv[1]), &emailAddress) | |
| 				*grants = append(*grants, &s3.Grant{ | |
| 					Grantee: &s3.Grantee{ | |
| 						Type:         &s3_constants.GrantTypeAmazonCustomerByEmail, | |
| 						EmailAddress: &emailAddress, | |
| 					}, | |
| 					Permission: &permission, | |
| 				}) | |
| 			case "uri": | |
| 				var groupName string | |
| 				_ = json.Unmarshal([]byte(kv[1]), &groupName) | |
| 				*grants = append(*grants, &s3.Grant{ | |
| 					Grantee: &s3.Grantee{ | |
| 						Type: &s3_constants.GrantTypeGroup, | |
| 						URI:  &groupName, | |
| 					}, | |
| 					Permission: &permission, | |
| 				}) | |
| 			} | |
| 		} | |
| 	} | |
| 	return s3err.ErrNone | |
| 
 | |
| } | |
| 
 | |
| func ParseCannedAclHeader(bucketOwnership, bucketOwnerId, accountId, cannedAcl string, putAcl bool) (ownerId string, grants []*s3.Grant, err s3err.ErrorCode) { | |
| 	err = s3err.ErrNone | |
| 	ownerId = accountId | |
| 
 | |
| 	//objectWrite automatically has full control on current object | |
| 	objectWriterFullControl := &s3.Grant{ | |
| 		Grantee: &s3.Grantee{ | |
| 			ID:   &accountId, | |
| 			Type: &s3_constants.GrantTypeCanonicalUser, | |
| 		}, | |
| 		Permission: &s3_constants.PermissionFullControl, | |
| 	} | |
| 
 | |
| 	switch cannedAcl { | |
| 	case s3_constants.CannedAclPrivate: | |
| 		grants = append(grants, objectWriterFullControl) | |
| 	case s3_constants.CannedAclPublicRead: | |
| 		grants = append(grants, objectWriterFullControl) | |
| 		grants = append(grants, s3_constants.PublicRead...) | |
| 	case s3_constants.CannedAclPublicReadWrite: | |
| 		grants = append(grants, objectWriterFullControl) | |
| 		grants = append(grants, s3_constants.PublicReadWrite...) | |
| 	case s3_constants.CannedAclAuthenticatedRead: | |
| 		grants = append(grants, objectWriterFullControl) | |
| 		grants = append(grants, s3_constants.AuthenticatedRead...) | |
| 	case s3_constants.CannedAclLogDeliveryWrite: | |
| 		grants = append(grants, objectWriterFullControl) | |
| 		grants = append(grants, s3_constants.LogDeliveryWrite...) | |
| 	case s3_constants.CannedAclBucketOwnerRead: | |
| 		grants = append(grants, objectWriterFullControl) | |
| 		if bucketOwnerId != "" && bucketOwnerId != accountId { | |
| 			grants = append(grants, | |
| 				&s3.Grant{ | |
| 					Grantee: &s3.Grantee{ | |
| 						Type: &s3_constants.GrantTypeCanonicalUser, | |
| 						ID:   &bucketOwnerId, | |
| 					}, | |
| 					Permission: &s3_constants.PermissionRead, | |
| 				}) | |
| 		} | |
| 	case s3_constants.CannedAclBucketOwnerFullControl: | |
| 		if bucketOwnerId != "" { | |
| 			// if set ownership to 'BucketOwnerPreferred' when upload object, the bucket owner will be the object owner | |
| 			if !putAcl && bucketOwnership == s3_constants.OwnershipBucketOwnerPreferred { | |
| 				ownerId = bucketOwnerId | |
| 				grants = append(grants, | |
| 					&s3.Grant{ | |
| 						Grantee: &s3.Grantee{ | |
| 							Type: &s3_constants.GrantTypeCanonicalUser, | |
| 							ID:   &bucketOwnerId, | |
| 						}, | |
| 						Permission: &s3_constants.PermissionFullControl, | |
| 					}) | |
| 			} else { | |
| 				grants = append(grants, objectWriterFullControl) | |
| 				if accountId != bucketOwnerId { | |
| 					grants = append(grants, | |
| 						&s3.Grant{ | |
| 							Grantee: &s3.Grantee{ | |
| 								Type: &s3_constants.GrantTypeCanonicalUser, | |
| 								ID:   &bucketOwnerId, | |
| 							}, | |
| 							Permission: &s3_constants.PermissionFullControl, | |
| 						}) | |
| 				} | |
| 			} | |
| 		} | |
| 	case s3_constants.CannedAclAwsExecRead: | |
| 		err = s3err.ErrNotImplemented | |
| 	default: | |
| 		err = s3err.ErrInvalidRequest | |
| 	} | |
| 	return | |
| } | |
| 
 | |
| // ValidateAndTransferGrants validate grant & transfer Email-Grant to Id-Grant | |
| func ValidateAndTransferGrants(accountManager AccountManager, grants []*s3.Grant) ([]*s3.Grant, s3err.ErrorCode) { | |
| 	var result []*s3.Grant | |
| 	for _, grant := range grants { | |
| 		grantee := grant.Grantee | |
| 		if grantee == nil || grantee.Type == nil { | |
| 			glog.Warning("invalid grantee! grantee or granteeType is nil") | |
| 			return nil, s3err.ErrInvalidRequest | |
| 		} | |
| 
 | |
| 		switch *grantee.Type { | |
| 		case s3_constants.GrantTypeGroup: | |
| 			if grantee.URI == nil { | |
| 				glog.Warning("invalid group grantee! group URI is nil") | |
| 				return nil, s3err.ErrInvalidRequest | |
| 			} | |
| 			ok := s3_constants.ValidateGroup(*grantee.URI) | |
| 			if !ok { | |
| 				glog.Warningf("invalid group grantee! group name[%s] is not valid", *grantee.URI) | |
| 				return nil, s3err.ErrInvalidRequest | |
| 			} | |
| 			result = append(result, grant) | |
| 		case s3_constants.GrantTypeCanonicalUser: | |
| 			if grantee.ID == nil { | |
| 				glog.Warning("invalid canonical grantee! account id is nil") | |
| 				return nil, s3err.ErrInvalidRequest | |
| 			} | |
| 			name := accountManager.GetAccountNameById(*grantee.ID) | |
| 			if len(name) == 0 { | |
| 				glog.Warningf("invalid canonical grantee! account id[%s] is not exists", *grantee.ID) | |
| 				return nil, s3err.ErrInvalidRequest | |
| 			} | |
| 			result = append(result, grant) | |
| 		case s3_constants.GrantTypeAmazonCustomerByEmail: | |
| 			if grantee.EmailAddress == nil { | |
| 				glog.Warning("invalid email grantee! email address is nil") | |
| 				return nil, s3err.ErrInvalidRequest | |
| 			} | |
| 			accountId := accountManager.GetAccountIdByEmail(*grantee.EmailAddress) | |
| 			if len(accountId) == 0 { | |
| 				glog.Warningf("invalid email grantee! email address[%s] is not exists", *grantee.EmailAddress) | |
| 				return nil, s3err.ErrInvalidRequest | |
| 			} | |
| 			result = append(result, &s3.Grant{ | |
| 				Grantee: &s3.Grantee{ | |
| 					Type: &s3_constants.GrantTypeCanonicalUser, | |
| 					ID:   &accountId, | |
| 				}, | |
| 				Permission: grant.Permission, | |
| 			}) | |
| 		default: | |
| 			return nil, s3err.ErrInvalidRequest | |
| 		} | |
| 	} | |
| 	return result, s3err.ErrNone | |
| } | |
| 
 | |
| // DetermineReqGrants generates the grant set (Grants) according to accountId and reqPermission. | |
| func DetermineReqGrants(accountId, aclAction string) (grants []*s3.Grant) { | |
| 	// group grantee (AllUsers) | |
| 	grants = append(grants, &s3.Grant{ | |
| 		Grantee: &s3.Grantee{ | |
| 			Type: &s3_constants.GrantTypeGroup, | |
| 			URI:  &s3_constants.GranteeGroupAllUsers, | |
| 		}, | |
| 		Permission: &aclAction, | |
| 	}) | |
| 	grants = append(grants, &s3.Grant{ | |
| 		Grantee: &s3.Grantee{ | |
| 			Type: &s3_constants.GrantTypeGroup, | |
| 			URI:  &s3_constants.GranteeGroupAllUsers, | |
| 		}, | |
| 		Permission: &s3_constants.PermissionFullControl, | |
| 	}) | |
| 
 | |
| 	// canonical grantee (accountId) | |
| 	grants = append(grants, &s3.Grant{ | |
| 		Grantee: &s3.Grantee{ | |
| 			Type: &s3_constants.GrantTypeCanonicalUser, | |
| 			ID:   &accountId, | |
| 		}, | |
| 		Permission: &aclAction, | |
| 	}) | |
| 	grants = append(grants, &s3.Grant{ | |
| 		Grantee: &s3.Grantee{ | |
| 			Type: &s3_constants.GrantTypeCanonicalUser, | |
| 			ID:   &accountId, | |
| 		}, | |
| 		Permission: &s3_constants.PermissionFullControl, | |
| 	}) | |
| 
 | |
| 	// group grantee (AuthenticateUsers) | |
| 	if accountId != s3_constants.AccountAnonymousId { | |
| 		grants = append(grants, &s3.Grant{ | |
| 			Grantee: &s3.Grantee{ | |
| 				Type: &s3_constants.GrantTypeGroup, | |
| 				URI:  &s3_constants.GranteeGroupAuthenticatedUsers, | |
| 			}, | |
| 			Permission: &aclAction, | |
| 		}) | |
| 		grants = append(grants, &s3.Grant{ | |
| 			Grantee: &s3.Grantee{ | |
| 				Type: &s3_constants.GrantTypeGroup, | |
| 				URI:  &s3_constants.GranteeGroupAuthenticatedUsers, | |
| 			}, | |
| 			Permission: &s3_constants.PermissionFullControl, | |
| 		}) | |
| 	} | |
| 	return | |
| } | |
| 
 | |
| func SetAcpOwnerHeader(r *http.Request, acpOwnerId string) { | |
| 	r.Header.Set(s3_constants.ExtAmzOwnerKey, acpOwnerId) | |
| } | |
| 
 | |
| func GetAcpOwner(entryExtended map[string][]byte, defaultOwner string) string { | |
| 	ownerIdBytes, ok := entryExtended[s3_constants.ExtAmzOwnerKey] | |
| 	if ok && len(ownerIdBytes) > 0 { | |
| 		return string(ownerIdBytes) | |
| 	} | |
| 	return defaultOwner | |
| } | |
| 
 | |
| func SetAcpGrantsHeader(r *http.Request, acpGrants []*s3.Grant) { | |
| 	if len(acpGrants) > 0 { | |
| 		a, err := json.Marshal(acpGrants) | |
| 		if err == nil { | |
| 			r.Header.Set(s3_constants.ExtAmzAclKey, string(a)) | |
| 		} else { | |
| 			glog.Warning("Marshal acp grants err", err) | |
| 		} | |
| 	} | |
| } | |
| 
 | |
| // GetAcpGrants return grants parsed from entry | |
| func GetAcpGrants(entryExtended map[string][]byte) []*s3.Grant { | |
| 	acpBytes, ok := entryExtended[s3_constants.ExtAmzAclKey] | |
| 	if ok && len(acpBytes) > 0 { | |
| 		var grants []*s3.Grant | |
| 		err := json.Unmarshal(acpBytes, &grants) | |
| 		if err == nil { | |
| 			return grants | |
| 		} | |
| 	} | |
| 	return nil | |
| } | |
| 
 | |
| // AssembleEntryWithAcp fill entry with owner and grants | |
| func AssembleEntryWithAcp(objectEntry *filer_pb.Entry, objectOwner string, grants []*s3.Grant) s3err.ErrorCode { | |
| 	if objectEntry.Extended == nil { | |
| 		objectEntry.Extended = make(map[string][]byte) | |
| 	} | |
| 
 | |
| 	if len(objectOwner) > 0 { | |
| 		objectEntry.Extended[s3_constants.ExtAmzOwnerKey] = []byte(objectOwner) | |
| 	} else { | |
| 		delete(objectEntry.Extended, s3_constants.ExtAmzOwnerKey) | |
| 	} | |
| 
 | |
| 	if len(grants) > 0 { | |
| 		grantsBytes, err := json.Marshal(grants) | |
| 		if err != nil { | |
| 			glog.Warning("assemble acp to entry:", err) | |
| 			return s3err.ErrInvalidRequest | |
| 		} | |
| 		objectEntry.Extended[s3_constants.ExtAmzAclKey] = grantsBytes | |
| 	} else { | |
| 		delete(objectEntry.Extended, s3_constants.ExtAmzAclKey) | |
| 	} | |
| 
 | |
| 	return s3err.ErrNone | |
| } | |
| 
 | |
| // GrantEquals Compare whether two Grants are equal in meaning, not completely | |
| // equal (compare Grantee.Type and the corresponding Value for equality, other | |
| // fields of Grantee are ignored) | |
| func GrantEquals(a, b *s3.Grant) bool { | |
| 	// grant | |
| 	if a == b { | |
| 		return true | |
| 	} | |
| 
 | |
| 	if a == nil || b == nil { | |
| 		return false | |
| 	} | |
| 
 | |
| 	// grant.Permission | |
| 	if a.Permission != b.Permission { | |
| 		if a.Permission == nil || b.Permission == nil { | |
| 			return false | |
| 		} | |
| 
 | |
| 		if *a.Permission != *b.Permission { | |
| 			return false | |
| 		} | |
| 	} | |
| 
 | |
| 	// grant.Grantee | |
| 	ag := a.Grantee | |
| 	bg := b.Grantee | |
| 	if ag != bg { | |
| 		if ag == nil || bg == nil { | |
| 			return false | |
| 		} | |
| 		// grantee.Type | |
| 		if ag.Type != bg.Type { | |
| 			if ag.Type == nil || bg.Type == nil { | |
| 				return false | |
| 			} | |
| 			if *ag.Type != *bg.Type { | |
| 				return false | |
| 			} | |
| 		} | |
| 		// value corresponding to granteeType | |
| 		if ag.Type != nil { | |
| 			switch *ag.Type { | |
| 			case s3_constants.GrantTypeGroup: | |
| 				if ag.URI != bg.URI { | |
| 					if ag.URI == nil || bg.URI == nil { | |
| 						return false | |
| 					} | |
| 
 | |
| 					if *ag.URI != *bg.URI { | |
| 						return false | |
| 					} | |
| 				} | |
| 			case s3_constants.GrantTypeCanonicalUser: | |
| 				if ag.ID != bg.ID { | |
| 					if ag.ID == nil || bg.ID == nil { | |
| 						return false | |
| 					} | |
| 
 | |
| 					if *ag.ID != *bg.ID { | |
| 						return false | |
| 					} | |
| 				} | |
| 			case s3_constants.GrantTypeAmazonCustomerByEmail: | |
| 				if ag.EmailAddress != bg.EmailAddress { | |
| 					if ag.EmailAddress == nil || bg.EmailAddress == nil { | |
| 						return false | |
| 					} | |
| 
 | |
| 					if *ag.EmailAddress != *bg.EmailAddress { | |
| 						return false | |
| 					} | |
| 				} | |
| 			} | |
| 		} | |
| 	} | |
| 	return true | |
| }
 |