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