##// END OF EJS Templates
file-store: small code cleanups.
marcink -
r4012:b20c076b default
parent child Browse files
Show More
@@ -0,0 +1,19 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
1 NO CONTENT: file renamed from rhodecode/apps/file_store/local_store.py to rhodecode/apps/file_store/backends/local_store.py
@@ -1,54 +1,54 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import uuid
23 23
24 24 import pathlib2
25 25
26 26
27 27 def get_file_storage(settings):
28 from rhodecode.apps.file_store.local_store import LocalFileStorage
28 from rhodecode.apps.file_store.backends.local_store import LocalFileStorage
29 29 from rhodecode.apps.file_store import config_keys
30 30 store_path = settings.get(config_keys.store_path)
31 31 return LocalFileStorage(base_path=store_path)
32 32
33 33
34 34 def splitext(filename):
35 35 ext = ''.join(pathlib2.Path(filename).suffixes)
36 36 return filename, ext
37 37
38 38
39 39 def uid_filename(filename, randomized=True):
40 40 """
41 41 Generates a randomized or stable (uuid) filename,
42 42 preserving the original extension.
43 43
44 44 :param filename: the original filename
45 45 :param randomized: define if filename should be stable (sha1 based) or randomized
46 46 """
47 47
48 48 _, ext = splitext(filename)
49 49 if randomized:
50 50 uid = uuid.uuid4()
51 51 else:
52 52 hash_key = '{}.{}'.format(filename, 'store')
53 53 uid = uuid.uuid5(uuid.NAMESPACE_URL, hash_key)
54 54 return str(uid) + ext.lower()
@@ -1,174 +1,174 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20 import logging
21 21
22 22 from pyramid.view import view_config
23 23 from pyramid.response import FileResponse
24 24 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
25 25
26 26 from rhodecode.apps._base import BaseAppView
27 27 from rhodecode.apps.file_store import utils
28 28 from rhodecode.apps.file_store.exceptions import (
29 29 FileNotAllowedException, FileOverSizeException)
30 30
31 31 from rhodecode.lib import helpers as h
32 32 from rhodecode.lib import audit_logger
33 33 from rhodecode.lib.auth import (
34 34 CSRFRequired, NotAnonymous, HasRepoPermissionAny, HasRepoGroupPermissionAny,
35 35 LoginRequired)
36 36 from rhodecode.model.db import Session, FileStore, UserApiKeys
37 37
38 38 log = logging.getLogger(__name__)
39 39
40 40
41 41 class FileStoreView(BaseAppView):
42 42 upload_key = 'store_file'
43 43
44 44 def load_default_context(self):
45 45 c = self._get_local_tmpl_context()
46 46 self.storage = utils.get_file_storage(self.request.registry.settings)
47 47 return c
48 48
49 def _serve_file(self, file_uid):
50
51 if not self.storage.exists(file_uid):
52 store_path = self.storage.store_path(file_uid)
53 log.debug('File with FID:%s not found in the store under `%s`',
54 file_uid, store_path)
55 raise HTTPNotFound()
56
57 db_obj = FileStore().query().filter(FileStore.file_uid == file_uid).scalar()
58 if not db_obj:
59 raise HTTPNotFound()
60
61 # private upload for user
62 if db_obj.check_acl and db_obj.scope_user_id:
63 log.debug('Artifact: checking scope access for bound artifact user: `%s`',
64 db_obj.scope_user_id)
65 user = db_obj.user
66 if self._rhodecode_db_user.user_id != user.user_id:
67 log.warning('Access to file store object forbidden')
68 raise HTTPNotFound()
69
70 # scoped to repository permissions
71 if db_obj.check_acl and db_obj.scope_repo_id:
72 log.debug('Artifact: checking scope access for bound artifact repo: `%s`',
73 db_obj.scope_repo_id)
74 repo = db_obj.repo
75 perm_set = ['repository.read', 'repository.write', 'repository.admin']
76 has_perm = HasRepoPermissionAny(*perm_set)(repo.repo_name, 'FileStore check')
77 if not has_perm:
78 log.warning('Access to file store object `%s` forbidden', file_uid)
79 raise HTTPNotFound()
80
81 # scoped to repository group permissions
82 if db_obj.check_acl and db_obj.scope_repo_group_id:
83 log.debug('Artifact: checking scope access for bound artifact repo group: `%s`',
84 db_obj.scope_repo_group_id)
85 repo_group = db_obj.repo_group
86 perm_set = ['group.read', 'group.write', 'group.admin']
87 has_perm = HasRepoGroupPermissionAny(*perm_set)(repo_group.group_name, 'FileStore check')
88 if not has_perm:
89 log.warning('Access to file store object `%s` forbidden', file_uid)
90 raise HTTPNotFound()
91
92 FileStore.bump_access_counter(file_uid)
93
94 file_path = self.storage.store_path(file_uid)
95 return FileResponse(file_path)
96
49 97 @LoginRequired()
50 98 @NotAnonymous()
51 99 @CSRFRequired()
52 100 @view_config(route_name='upload_file', request_method='POST', renderer='json_ext')
53 101 def upload_file(self):
54 102 self.load_default_context()
55 103 file_obj = self.request.POST.get(self.upload_key)
56 104
57 105 if file_obj is None:
58 106 return {'store_fid': None,
59 107 'access_path': None,
60 108 'error': '{} data field is missing'.format(self.upload_key)}
61 109
62 110 if not hasattr(file_obj, 'filename'):
63 111 return {'store_fid': None,
64 112 'access_path': None,
65 113 'error': 'filename cannot be read from the data field'}
66 114
67 115 filename = file_obj.filename
68 116
69 117 metadata = {
70 118 'user_uploaded': {'username': self._rhodecode_user.username,
71 119 'user_id': self._rhodecode_user.user_id,
72 120 'ip': self._rhodecode_user.ip_addr}}
73 121 try:
74 122 store_uid, metadata = self.storage.save_file(
75 123 file_obj.file, filename, extra_metadata=metadata)
76 124 except FileNotAllowedException:
77 125 return {'store_fid': None,
78 126 'access_path': None,
79 127 'error': 'File {} is not allowed.'.format(filename)}
80 128
81 129 except FileOverSizeException:
82 130 return {'store_fid': None,
83 131 'access_path': None,
84 132 'error': 'File {} is exceeding allowed limit.'.format(filename)}
85 133
86 134 try:
87 135 entry = FileStore.create(
88 136 file_uid=store_uid, filename=metadata["filename"],
89 137 file_hash=metadata["sha256"], file_size=metadata["size"],
90 138 file_description=u'upload attachment',
91 139 check_acl=False, user_id=self._rhodecode_user.user_id
92 140 )
93 141 Session().add(entry)
94 142 Session().commit()
95 143 log.debug('Stored upload in DB as %s', entry)
96 144 except Exception:
97 145 log.exception('Failed to store file %s', filename)
98 146 return {'store_fid': None,
99 147 'access_path': None,
100 148 'error': 'File {} failed to store in DB.'.format(filename)}
101 149
102 150 return {'store_fid': store_uid,
103 151 'access_path': h.route_path('download_file', fid=store_uid)}
104 152
105 def _serve_file(self, file_uid):
106
107 if not self.storage.exists(file_uid):
108 store_path = self.storage.store_path(file_uid)
109 log.debug('File with FID:%s not found in the store under `%s`',
110 file_uid, store_path)
111 raise HTTPNotFound()
112
113 db_obj = FileStore().query().filter(FileStore.file_uid == file_uid).scalar()
114 if not db_obj:
115 raise HTTPNotFound()
116
117 # private upload for user
118 if db_obj.check_acl and db_obj.scope_user_id:
119 log.debug('Artifact: checking scope access for bound artifact user: `%s`',
120 db_obj.scope_user_id)
121 user = db_obj.user
122 if self._rhodecode_db_user.user_id != user.user_id:
123 log.warning('Access to file store object forbidden')
124 raise HTTPNotFound()
125
126 # scoped to repository permissions
127 if db_obj.check_acl and db_obj.scope_repo_id:
128 log.debug('Artifact: checking scope access for bound artifact repo: `%s`',
129 db_obj.scope_repo_id)
130 repo = db_obj.repo
131 perm_set = ['repository.read', 'repository.write', 'repository.admin']
132 has_perm = HasRepoPermissionAny(*perm_set)(repo.repo_name, 'FileStore check')
133 if not has_perm:
134 log.warning('Access to file store object `%s` forbidden', file_uid)
135 raise HTTPNotFound()
136
137 # scoped to repository group permissions
138 if db_obj.check_acl and db_obj.scope_repo_group_id:
139 log.debug('Artifact: checking scope access for bound artifact repo group: `%s`',
140 db_obj.scope_repo_group_id)
141 repo_group = db_obj.repo_group
142 perm_set = ['group.read', 'group.write', 'group.admin']
143 has_perm = HasRepoGroupPermissionAny(*perm_set)(repo_group.group_name, 'FileStore check')
144 if not has_perm:
145 log.warning('Access to file store object `%s` forbidden', file_uid)
146 raise HTTPNotFound()
147
148 FileStore.bump_access_counter(file_uid)
149
150 file_path = self.storage.store_path(file_uid)
151 return FileResponse(file_path)
152
153 153 # ACL is checked by scopes, if no scope the file is accessible to all
154 154 @view_config(route_name='download_file')
155 155 def download_file(self):
156 156 self.load_default_context()
157 157 file_uid = self.request.matchdict['fid']
158 158 log.debug('Requesting FID:%s from store %s', file_uid, self.storage)
159 159 return self._serve_file(file_uid)
160 160
161 161 # in addition to @LoginRequired ACL is checked by scopes
162 162 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_ARTIFACT_DOWNLOAD])
163 163 @NotAnonymous()
164 164 @view_config(route_name='download_file_by_token')
165 165 def download_file_by_token(self):
166 166 """
167 167 Special view that allows to access the download file by special URL that
168 168 is stored inside the URL.
169 169
170 170 http://example.com/_file_store/token-download/TOKEN/FILE_UID
171 171 """
172 172 self.load_default_context()
173 173 file_uid = self.request.matchdict['fid']
174 174 return self._serve_file(file_uid)
General Comments 0
You need to be logged in to leave comments. Login now