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 |