| 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. |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after 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 |