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 |