| OLD | NEW |
| 1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Cloud Endpoints API for Content Addressable Storage.""" | 5 """Cloud Endpoints API for Content Addressable Storage.""" |
| 6 | 6 |
| 7 import endpoints | 7 import endpoints |
| 8 | 8 |
| 9 from protorpc import message_types | 9 from protorpc import message_types |
| 10 from protorpc import messages | 10 from protorpc import messages |
| 11 from protorpc import remote | 11 from protorpc import remote |
| 12 | 12 |
| 13 from components import auth | 13 from components import auth |
| 14 | 14 |
| 15 from . import impl | 15 from . import impl |
| 16 | 16 |
| 17 # TODO(vadimsh): Improve authorization scheme. | |
| 18 | 17 |
| 19 # This is used by endpoints indirectly. | 18 # This is used by endpoints indirectly. |
| 20 package = 'cipd' | 19 package = 'cipd' |
| 21 | 20 |
| 22 | 21 |
| 23 class BeginUploadResponse(messages.Message): | 22 class BeginUploadResponse(messages.Message): |
| 24 class Status(messages.Enum): | 23 class Status(messages.Enum): |
| 25 # New upload session has started. | 24 # New upload session has started. |
| 26 SUCCESS = 1 | 25 SUCCESS = 1 |
| 27 # Such file is already uploaded to the store. | 26 # Such file is already uploaded to the store. |
| 28 ALREADY_UPLOADED = 2 | 27 ALREADY_UPLOADED = 2 |
| 29 # Some unexpected fatal error happened. | 28 # Some unexpected fatal error happened. |
| 30 ERROR = 3 | 29 ERROR = 3 |
| 31 | 30 |
| 32 # Status of this operation, defines what other fields to expect. | 31 # Status of this operation, defines what other fields to expect. |
| 33 status = messages.EnumField('BeginUploadResponse.Status', 1, required=True) | 32 status = messages.EnumField(Status, 1, required=True) |
| 34 | 33 |
| 35 # For SUCCESS status, a unique identifier of the upload operation. | 34 # For SUCCESS status, a unique identifier of the upload operation. |
| 36 upload_session_id = messages.StringField(2, required=False) | 35 upload_session_id = messages.StringField(2, required=False) |
| 37 # For SUCCESS status, URL to PUT file body to via resumable upload protocol. | 36 # For SUCCESS status, URL to PUT file body to via resumable upload protocol. |
| 38 upload_url = messages.StringField(3, required=False) | 37 upload_url = messages.StringField(3, required=False) |
| 39 | 38 |
| 40 # For ERROR status, a error message. | 39 # For ERROR status, a error message. |
| 41 error_message = messages.StringField(4, required=False) | 40 error_message = messages.StringField(4, required=False) |
| 42 | 41 |
| 43 | 42 |
| 44 class FinishUploadResponse(messages.Message): | 43 class FinishUploadResponse(messages.Message): |
| 45 class Status(messages.Enum): | 44 class Status(messages.Enum): |
| 46 # Upload session never existed or already expired. | 45 # Upload session never existed or already expired. |
| 47 MISSING = impl.UploadSession.STATUS_MISSING | 46 MISSING = impl.UploadSession.STATUS_MISSING |
| 48 # Client is still uploading the file. | 47 # Client is still uploading the file. |
| 49 UPLOADING = impl.UploadSession.STATUS_UPLOADING | 48 UPLOADING = impl.UploadSession.STATUS_UPLOADING |
| 50 # Server is verifying the hash of the uploaded file. | 49 # Server is verifying the hash of the uploaded file. |
| 51 VERIFYING = impl.UploadSession.STATUS_VERIFYING | 50 VERIFYING = impl.UploadSession.STATUS_VERIFYING |
| 52 # The file is in the store and visible by all clients. Final state. | 51 # The file is in the store and visible by all clients. Final state. |
| 53 PUBLISHED = impl.UploadSession.STATUS_PUBLISHED | 52 PUBLISHED = impl.UploadSession.STATUS_PUBLISHED |
| 54 # Some other unexpected fatal error happened. | 53 # Some other unexpected fatal error happened. |
| 55 ERROR = impl.UploadSession.STATUS_ERROR | 54 ERROR = impl.UploadSession.STATUS_ERROR |
| 56 | 55 |
| 57 # Status of the upload operation. | 56 # Status of the upload operation. |
| 58 status = messages.EnumField('FinishUploadResponse.Status', 1, required=True) | 57 status = messages.EnumField(Status, 1, required=True) |
| 59 # Optional error message for STATUS_ERROR status. | 58 # Optional error message for STATUS_ERROR status. |
| 60 error_message = messages.StringField(2, required=False) | 59 error_message = messages.StringField(2, required=False) |
| 61 | 60 |
| 62 | 61 |
| 63 # int status -> Enum status. | 62 # int status -> Enum status. |
| 64 _UPLOAD_STATUS_MAPPING = { | 63 _UPLOAD_STATUS_MAPPING = { |
| 65 getattr(impl.UploadSession, k): getattr( | 64 getattr(impl.UploadSession, k): getattr( |
| 66 FinishUploadResponse.Status, k[len('STATUS_'):]) | 65 FinishUploadResponse.Status, k[len('STATUS_'):]) |
| 67 for k in dir(impl.UploadSession) if k.startswith('STATUS_') | 66 for k in dir(impl.UploadSession) if k.startswith('STATUS_') |
| 68 } | 67 } |
| (...skipping 13 matching lines...) Expand all Loading... |
| 82 hash_algo = messages.StringField(1, required=True), | 81 hash_algo = messages.StringField(1, required=True), |
| 83 # Hex hash digest of a file client wants to upload. | 82 # Hex hash digest of a file client wants to upload. |
| 84 file_hash = messages.StringField(2, required=True)) | 83 file_hash = messages.StringField(2, required=True)) |
| 85 | 84 |
| 86 @auth.endpoints_method( | 85 @auth.endpoints_method( |
| 87 BEGIN_UPLOAD_RESOURCE_CONTAINER, | 86 BEGIN_UPLOAD_RESOURCE_CONTAINER, |
| 88 BeginUploadResponse, | 87 BeginUploadResponse, |
| 89 path='upload/{hash_algo}/{file_hash}', | 88 path='upload/{hash_algo}/{file_hash}', |
| 90 http_method='POST', | 89 http_method='POST', |
| 91 name='beginUpload') | 90 name='beginUpload') |
| 92 @auth.require(lambda: not auth.get_current_identity().is_anonymous) | 91 @auth.require(auth.is_admin) |
| 93 def begin_upload(self, request): | 92 def begin_upload(self, request): |
| 94 """Initiates an upload operation if file is missing. | 93 """Initiates an upload operation if file is missing. |
| 95 | 94 |
| 96 Once initiated the client is then responsible for uploading the file to | 95 Once initiated the client is then responsible for uploading the file to |
| 97 temporary location (returned as 'upload_url') and finalizing the upload | 96 temporary location (returned as 'upload_url') and finalizing the upload |
| 98 with call to 'finishUpload'. | 97 with call to 'finishUpload'. |
| 99 | 98 |
| 100 If file is already in the store, returns ALREADY_UPLOADED status. | 99 If file is already in the store, returns ALREADY_UPLOADED status. |
| 100 |
| 101 This method is not intended to be used directly by all clients (only by |
| 102 admins in case some files has to be injected into CAS store directly). Use |
| 103 PackageRepositoryApi.register_package instead to initiate an upload of some |
| 104 package and get upload_url and upload_session_id. |
| 101 """ | 105 """ |
| 102 if not impl.is_supported_hash_algo(request.hash_algo): | 106 if not impl.is_supported_hash_algo(request.hash_algo): |
| 103 raise endpoints.BadRequestException('Unsupported hash algo') | 107 raise endpoints.BadRequestException('Unsupported hash algo') |
| 104 if not impl.is_valid_hash_digest(request.hash_algo, request.file_hash): | 108 if not impl.is_valid_hash_digest(request.hash_algo, request.file_hash): |
| 105 raise endpoints.BadRequestException('Invalid hash digest format') | 109 raise endpoints.BadRequestException('Invalid hash digest format') |
| 106 | 110 |
| 107 service = impl.get_cas_service() | 111 service = impl.get_cas_service() |
| 108 if service is None: | 112 if service is None: |
| 109 raise endpoints.InternalServerErrorException('Service is not configured') | 113 raise endpoints.InternalServerErrorException('Service is not configured') |
| 110 | 114 |
| (...skipping 23 matching lines...) Expand all Loading... |
| 134 http_method='POST', | 138 http_method='POST', |
| 135 name='finishUpload') | 139 name='finishUpload') |
| 136 @auth.require(lambda: not auth.get_current_identity().is_anonymous) | 140 @auth.require(lambda: not auth.get_current_identity().is_anonymous) |
| 137 def finish_upload(self, request): | 141 def finish_upload(self, request): |
| 138 """Finishes pending upload or queries its status. | 142 """Finishes pending upload or queries its status. |
| 139 | 143 |
| 140 Client should finalize Google Storage upload session first. Once GS upload | 144 Client should finalize Google Storage upload session first. Once GS upload |
| 141 is finalized and 'finishUpload' is called, the server starts hash | 145 is finalized and 'finishUpload' is called, the server starts hash |
| 142 verification. Uploading client will get 'VERIFYING' status response. It | 146 verification. Uploading client will get 'VERIFYING' status response. It |
| 143 can continue polling on this method until server returns 'PUBLISHED' status. | 147 can continue polling on this method until server returns 'PUBLISHED' status. |
| 148 |
| 149 upload_session_id implicitly authorizes the request. |
| 144 """ | 150 """ |
| 145 service = impl.get_cas_service() | 151 service = impl.get_cas_service() |
| 146 if service is None: | 152 if service is None: |
| 147 raise endpoints.InternalServerErrorException('Service is not configured') | 153 raise endpoints.InternalServerErrorException('Service is not configured') |
| 148 | 154 |
| 149 # Verify the signature if upload_session_id and grab the session. Broken | 155 # Verify the signature if upload_session_id and grab the session. Broken |
| 150 # or expired signatures are treated in same way as missing upload sessions. | 156 # or expired signatures are treated in same way as missing upload sessions. |
| 151 # No need to provide more hits to the malicious caller. | 157 # No need to provide more hits to the malicious caller. |
| 152 upload_session = service.fetch_upload_session( | 158 upload_session = service.fetch_upload_session( |
| 153 request.upload_session_id, auth.get_current_identity()) | 159 request.upload_session_id, auth.get_current_identity()) |
| 154 if upload_session is None: | 160 if upload_session is None: |
| 155 return FinishUploadResponse(status=FinishUploadResponse.Status.MISSING) | 161 return FinishUploadResponse(status=FinishUploadResponse.Status.MISSING) |
| 156 | 162 |
| 157 # Start object verification task if necessary, returns updated copy of | 163 # Start object verification task if necessary, returns updated copy of |
| 158 # |upload_session| entity. | 164 # |upload_session| entity. |
| 159 upload_session = service.maybe_finish_upload(upload_session) | 165 upload_session = service.maybe_finish_upload(upload_session) |
| 160 | 166 |
| 161 response = FinishUploadResponse( | 167 response = FinishUploadResponse( |
| 162 status=_UPLOAD_STATUS_MAPPING[upload_session.status]) | 168 status=_UPLOAD_STATUS_MAPPING[upload_session.status]) |
| 163 if upload_session.status == impl.UploadSession.STATUS_ERROR: | 169 if upload_session.status == impl.UploadSession.STATUS_ERROR: |
| 164 response.error_message = upload_session.error_message or 'Unknown error' | 170 response.error_message = upload_session.error_message or 'Unknown error' |
| 165 return response | 171 return response |
| OLD | NEW |