Revision 56f3c759

b/contrib/migrate-db
166 166
            self.create_tags(headerid, nodeid, vserials)
167 167
            #set object's publicity
168 168
            if public:
169
                self.backend.permissions.public_set(object)
169
                self.backend.permissions.public_set(
170
                    object,
171
                    self.backend.public_url_min_length,
172
                    self.backend.public_url_alphabet
173
                )
170 174
            #set object's permissions
171 175
            self.create_permissions(headerid, object, username, is_folder=False)
172 176
    
b/snf-pithos-app/README
27 27

  
28 28
Configure in ``settings.py`` or a ``.conf`` file in ``/etc/synnefo`` if using snf-webproject.
29 29

  
30
===============================  =================================================  ============================================================
31
Name                             Default value                                      Description
32
===============================  =================================================  ============================================================
33
PITHOS_AUTHENTICATION_URL        \https://<astakos.host>/im/authenticate/           Astakos Authentication URL
34
PITHOS_AUTHENTICATION_USERS      A dictionary of sample users (token to username)   Set to empty or None to disable
35
PITHOS_ASTAKOS_COOKIE_NAME       _pithos2_a                                         Cookie name to retrieve fallback token
30
===============================  ================================================================   ============================================================
31
Name                             Default value                                                      Description
32
===============================  ================================================================   ============================================================
33
PITHOS_AUTHENTICATION_URL        \https://<astakos.host>/im/authenticate/                           Astakos Authentication URL
34
PITHOS_AUTHENTICATION_USERS      A dictionary of sample users (token to username)                   Set to empty or None to disable
35
PITHOS_ASTAKOS_COOKIE_NAME       _pithos2_a                                                         Cookie name to retrieve fallback token
36 36
PITHOS_BACKEND_DB_MODULE         pithos.backends.lib.sqlalchemy
37
PITHOS_BACKEND_DB_CONNECTION     sqlite:////tmp/pithos-backend.db                   SQLAlchemy database connection string
38
PITHOS_BACKEND_BLOCK_MODULE      pithos.backends.lib.hashfiler
39
PITHOS_BACKEND_BLOCK_PATH        /tmp/pithos-data/                                  Map and block storage path
40
PITHOS_BACKEND_BLOCK_UMASK       0o022                                              Map and block storage umask
41
PITHOS_BACKEND_QUEUE_MODULE      None                                               Use ``pithos.backends.lib.rabbitmq`` to enable
42
PITHOS_BACKEND_QUEUE_HOSTS       None                                               Format like [``amqp://guest:guest@localhost:5672``
37
PITHOS_BACKEND_DB_CONNECTION     sqlite:////tmp/pithos-backend.db                                   SQLAlchemy database connection string
38
PITHOS_BACKEND_BLOCK_MODULE      pithos.backends.lib.hashfile
39
PITHOS_BACKEND_BLOCK_PATH        /tmp/pithos-data/                                                  Map and block storage path
40
PITHOS_BACKEND_BLOCK_UMASK       0o022                                                              Map and block storage umask
41
PITHOS_BACKEND_QUEUE_MODULE      None                                                               Use ``pithos.backends.lib.rabbitmq`` to enable
42
PITHOS_BACKEND_QUEUE_HOSTS       None                                                               Format like [``amqp://guest:guest@localhost:5672``
43 43
PITHOS_BACKEND_QUEUE_EXCHANGE    pithos
44
PITHOS_BACKEND_QUOTA             50 GB (50 * 1024 ** 3)                             Default user quota
45
PITHOS_BACKEND_VERSIONING        auto                                               Default versioning policy for containers
46
PITHOS_BACKEND_FREE_VERSIONING   True                                               Default versioning debit policy (default free)
47
PITHOS_UPDATE_MD5                True                                               Update object checksums when using hashmaps
48
PITHOS_SERVICE_TOKEN             ''                                                 Service token acquired by the identity provider (astakos)
49
PITHOS_RADOS_STORAGE             False                                              Enables or disables secondary Pithos storage on RADOS
50
PITHOS_RADOS_POOL_BLOCKS         None                                               RADOS pool to be used for block storage
51
PITHOS_RADOS_POOL_MAPS           None                                               RADOS pool to be used for maps storage
52
PITHOS_TRANSLATE_UUIDS           False                                              Enables a ui compatibility layer for the introduction of UUIDs in identity management.
53
PITHOS_PROXY_USER_SERVICES       True                                               Whether to proxy user feedback and catalog services
54
PITHOS_USER_CATALOG_URL          \https://<astakos.host>/user_catalogs/             Astakos User Catalog URL
55
PITHOS_USER_FEEDBACK_URL         \https://<astakos.host>/feedback/                  Astakos User Feedback URL
56
PITHOS_USER_LOGIN_URL            \https://<astakos.host>/login/                     Astakos User Login URL
57
PITHOS_USE_QUOTAHOLDER           True                                               Enable quotaholder
58
PITHOS_QUOTAHOLDER_URL           ''                                                 Quotaholder URL
59
PITHOS_QUOTAHOLDER_TOKEN         ''                                                 Quotaholder token
60
===============================  =================================================  ============================================================
44
PITHOS_BACKEND_QUOTA             50 GB (50 * 1024 ** 3)                                             Default user quota
45
PITHOS_BACKEND_VERSIONING        auto                                                               Default versioning policy for containers
46
PITHOS_BACKEND_FREE_VERSIONING   True                                                               Default versioning debit policy (default free)
47
PITHOS_UPDATE_MD5                True                                                               Update object checksums when using hashmaps
48
PITHOS_SERVICE_TOKEN             ''                                                                 Service token acquired by the identity provider (astakos)
49
PITHOS_RADOS_STORAGE             False                                                              Enables or disables secondary Pithos storage on RADOS
50
PITHOS_RADOS_POOL_BLOCKS         None                                                               RADOS pool to be used for block storage
51
PITHOS_RADOS_POOL_MAPS           None                                                               RADOS pool to be used for maps storage
52
PITHOS_TRANSLATE_UUIDS           False                                                              Enables a ui compatibility layer for the introduction of UUIDs in identity management.
53
PITHOS_PROXY_USER_SERVICES       True                                                               Whether to proxy user feedback and catalog services
54
PITHOS_USER_CATALOG_URL          \https://<astakos.host>/user_catalogs/                             Astakos User Catalog URL
55
PITHOS_USER_FEEDBACK_URL         \https://<astakos.host>/feedback/                                  Astakos User Feedback URL
56
PITHOS_USER_LOGIN_URL            \https://<astakos.host>/login/                                     Astakos User Login URL
57
PITHOS_USE_QUOTAHOLDER           True                                                               Enable quotaholder
58
PITHOS_QUOTAHOLDER_URL           ''                                                                 Quotaholder URL
59
PITHOS_QUOTAHOLDER_TOKEN         ''                                                                 Quotaholder token
60
PITHOS_PUBLIC_URL_MIN_LENGTH     8                                                                  Public URL minimun length
61
PITHOS_PUBLIC_URL_ALPHABET       '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'   Public URL alphabet
62
===============================  ================================================================   ============================================================
61 63

  
62 64
To update checksums asynchronously, enable the queue, install snf-pithos-tools and use ``pithos-dispatcher``::
63 65

  
b/snf-pithos-app/conf/20-snf-pithos-app-settings.conf
51 51
#
52 52
# Tune the size of the http pool for the quotaholder client.
53 53
# It limits the maximum number of quota changing requests
54
# that pithos can serve. Extra requests will be blocked 
54
# that pithos can serve. Extra requests will be blocked
55 55
# until another has completed.
56 56
#
57 57
#PITHOS_QUOTAHOLDER_POOLSIZE = 200
58
#
59
# Set public url length
60
#PITHOS_PUBLIC_URL_MIN_LENGTH = 8
b/snf-pithos-app/pithos/api/public.py
44 44
                             validate_matching_preconditions,
45 45
                             object_data_response, api_method,
46 46
                             split_container_object_string)
47
from pithos.api.short_url import decode_url
48 47
from pithos.api.settings import AUTHENTICATION_URL, AUTHENTICATION_USERS
49 48

  
50 49

  
......
72 71
    try:
73 72
        v_account, v_container, v_object = request.backend.get_public(
74 73
            request.user_uniq,
75
            decode_url(v_public))
74
            v_public)
76 75
        meta = request.backend.get_object_meta(request.user_uniq, v_account,
77 76
                                               v_container, v_object, 'pithos')
78 77
        public = request.backend.get_object_public(
......
99 98
    #                       itemNotFound (404),
100 99
    #                       badRequest (400),
101 100
    #                       notModified (304)
101

  
102 102
    try:
103 103
        v_account, v_container, v_object = request.backend.get_public(
104 104
            request.user_uniq,
105
            decode_url(v_public))
105
            v_public)
106 106
        meta = request.backend.get_object_meta(request.user_uniq, v_account,
107 107
                                               v_container, v_object, 'pithos')
108 108
        public = request.backend.get_object_public(
b/snf-pithos-app/pithos/api/settings.py
65 65
QUOTAHOLDER_URL = getattr(settings, 'PITHOS_QUOTAHOLDER_URL', '')
66 66
QUOTAHOLDER_TOKEN = getattr(settings, 'PITHOS_QUOTAHOLDER_TOKEN', '')
67 67
QUOTAHOLDER_POOLSIZE = getattr(settings, 'PITHOS_QUOTAHOLDER_POOLSIZE', 200)
68

  
69
# Set public url length and alphabet
70
PUBLIC_URL_MIN_LENGTH =  getattr(settings, 'PITHOS_PUBLIC_URL_MIN_LENGTH', 8)
71
PUBLIC_URL_ALPHABET =  getattr(
72
    settings,
73
    'PITHOS_PUBLIC_URL_ALPHABET',
74
    '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
75
)
b/snf-pithos-app/pithos/api/swiss_army/__init__.py
194 194
        if public:
195 195
            # set destination object public
196 196
            fullpath = '/'.join([dest_account, dest_container, dest_name])
197
            self.backend.permissions.public_set(fullpath)
197
            self.backend.permissions.public_set(
198
                fullpath,
199
                self.backend.public_url_min_length,
200
                self.backend.public_url_alphabet
201
            )
198 202

  
199 203
    def _merge_account(self, src_account, dest_account, delete_src=False):
200 204
            # TODO: handle exceptions
b/snf-pithos-app/pithos/api/tests.py
1
# Copyright 2011 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

  
34
import unittest
35
import random
36
import string
37
import datetime
38
import time as _time
39

  
40
import pithos.api.settings as settings
41

  
42
from pithos.api.swiss_army import SwissArmy
43

  
44
def get_random_data(length=500):
45
    char_set = string.ascii_uppercase + string.digits
46
    return ''.join(random.choice(char_set) for x in xrange(length))
47

  
48
class TestPublic(unittest.TestCase):
49
    def setUp(self):
50
        self.utils = SwissArmy()
51
        self.backend = self.utils.backend
52
        self.utils.create_account('account')
53

  
54
    def tearDown(self):
55
        self.utils._delete_account('account')
56
        self.utils.cleanup()
57

  
58
    def assert_not_public_object(self, account, container, object):
59
        public = self.backend.get_object_public(
60
            account, account, container, object
61
        )
62
        self.assertTrue(public == None)
63
        self.assertRaises(
64
            NameError,
65
            self.backend.get_public,
66
            '$$account$$',
67
            public
68
        )
69
        self.assertRaises(
70
            Exception, self.backend._can_read,
71
            '$$account$$', account, container, object
72
        )
73
        return public
74

  
75
    def assert_public_object(self, account, container, object):
76
        public = self.backend.get_object_public(
77
            account, account, container, object
78
        )
79
        self.assertTrue(public != None)
80
        self.assertTrue(len(public) >= settings.PUBLIC_URL_MIN_LENGTH)
81
        self.assertTrue(set(public) <= set(settings.PUBLIC_URL_ALPHABET))
82
        self.assertEqual(
83
            self.backend.get_public('$$account$$', public),
84
            (account, container, object)
85
        )
86
        try:
87
            self.backend._can_read('$$account$$', account, container, object)
88
        except Exception:
89
            self.fail('Public object should be readable.')
90
        return public
91

  
92
    def test_set_object_public(self):
93
        self.utils.backend.put_container('account', 'account', 'container')
94
        data = get_random_data(int(random.random()))
95
        self.utils.create_update_object(
96
            'account',
97
            'container',
98
            'object',
99
            'application/octet-stream',
100
            data
101
        )
102
        self.assert_not_public_object('account', 'container', 'object')
103

  
104
        self.backend.permissions.public_set(
105
            'account/container/object',
106
            self.backend.public_url_min_length,
107
            self.backend.public_url_alphabet
108
        )
109
        self.assert_public_object('account', 'container', 'object')
110

  
111
    def test_set_twice(self):
112
        self.utils.backend.put_container('account', 'account', 'container')
113
        data = get_random_data(int(random.random()))
114
        self.utils.create_update_object(
115
            'account',
116
            'container',
117
            'object',
118
            'application/octet-stream',
119
            data
120
        )
121
        self.backend.permissions.public_set(
122
            'account/container/object',
123
            self.backend.public_url_min_length,
124
            self.backend.public_url_alphabet
125
        )
126
        public = self.assert_public_object('account', 'container', 'object')
127

  
128
        self.backend.permissions.public_set(
129
            'account/container/object',
130
            self.backend.public_url_min_length,
131
            self.backend.public_url_alphabet
132
        )
133
        public2 = self.assert_public_object('account', 'container', 'object')
134

  
135
        self.assertEqual(public, public2)
136

  
137
    def test_set_unset_set(self):
138
        self.utils.backend.put_container('account', 'account', 'container')
139
        data = get_random_data(int(random.random()))
140
        self.utils.create_update_object(
141
            'account',
142
            'container',
143
            'object',
144
            'application/octet-stream',
145
            data
146
        )
147
        self.backend.permissions.public_set(
148
            'account/container/object',
149
            self.backend.public_url_min_length,
150
            self.backend.public_url_alphabet
151
        )
152
        public = self.assert_public_object('account', 'container', 'object')
153

  
154
        self.backend.permissions.public_unset('account/container/object')
155
        self.assert_not_public_object('account', 'container', 'object')
156

  
157
        self.backend.permissions.public_set(
158
            'account/container/object',
159
            self.backend.public_url_min_length,
160
            self.backend.public_url_alphabet
161
        )
162
        public3 = self.assert_public_object('account', 'container', 'object')
163

  
164
        self.assertTrue(public != public3)
165

  
166
    def test_update_object_public(self):
167
        self.utils.backend.put_container('account', 'account', 'container')
168
        data = get_random_data(int(random.random()))
169
        self.utils.create_update_object(
170
            'account',
171
            'container',
172
            'object',
173
            'application/octet-stream',
174
            data
175
        )
176

  
177
        self.backend.update_object_public(
178
            'account', 'account', 'container', 'object', public=False
179
        )
180
        self.assert_not_public_object('account', 'container', 'object')
181

  
182
        self.backend.update_object_public(
183
            'account', 'account', 'container', 'object', public=True
184
        )
185
        public = self.assert_public_object('account', 'container', 'object')
186

  
187
        self.backend.update_object_public(
188
            'account', 'account', 'container', 'object', public=False
189
        )
190
        self.assert_not_public_object('account', 'container', 'object')
191

  
192
        self.backend.update_object_public(
193
            'account', 'account', 'container', 'object', public=True
194
        )
195
        new_public = self.assert_public_object('account', 'container', 'object')
196
        self.assertTrue(public != new_public)
197

  
198
    def test_delete_not_public_object(self):
199
        self.utils.backend.put_container('account', 'account', 'container')
200
        data = get_random_data(int(random.random()))
201
        self.utils.create_update_object(
202
            'account',
203
            'container',
204
            'object',
205
            'application/octet-stream',
206
            data
207
        )
208
        self.assert_not_public_object('account', 'container', 'object')
209

  
210
        self.backend.delete_object('account', 'account', 'container', 'object')
211

  
212
        self.assert_not_public_object('account', 'container', 'object')
213

  
214
    def test_delete_public_object(self):
215
        self.utils.backend.put_container('account', 'account', 'container')
216
        data = get_random_data(int(random.random()))
217
        self.utils.create_update_object(
218
            'account',
219
            'container',
220
            'object',
221
            'application/octet-stream',
222
            data
223
        )
224
        self.assert_not_public_object('account', 'container', 'object')
225

  
226
        self.backend.permissions.public_set(
227
            'account/container/object',
228
            self.backend.public_url_min_length,
229
            self.backend.public_url_alphabet
230
        )
231
        self.assert_public_object('account', 'container', 'object')
232

  
233
        self.backend.delete_object('account', 'account', 'container', 'object')
234
        self.assert_not_public_object('account', 'container', 'object')
235

  
236
    def test_delete_public_object_history(self):
237
        self.utils.backend.put_container('account', 'account', 'container')
238
        for i in range(random.randint(1, 10)):
239
            data = get_random_data(int(random.random()))
240
            self.utils.create_update_object(
241
                'account',
242
                'container',
243
                'object',
244
                'application/octet-stream',
245
                data
246
            )
247
            _time.sleep(1)
248
        versions = self.backend.list_versions(
249
            'account', 'account', 'container', 'object'
250
        )
251
        mtime = [int(i[1]) for i in versions]
252
        self.assert_not_public_object('account', 'container', 'object')
253

  
254
        self.backend.permissions.public_set(
255
            'account/container/object',
256
            self.backend.public_url_min_length,
257
            self.backend.public_url_alphabet
258
        )
259
        public = self.assert_public_object('account', 'container', 'object')
260

  
261
        i = random.randrange(len(mtime))
262
        self.backend.delete_object(
263
            'account', 'account', 'container', 'object', until=mtime[i]
264
        )
265
        self.assert_public_object('account', 'container', 'object')
266
        public = self.assert_public_object('account', 'container', 'object')
267

  
268
        _time.sleep(1)
269
        t = datetime.datetime.utcnow()
270
        now = int(_time.mktime(t.timetuple()))
271
        self.backend.delete_object(
272
            'account', 'account', 'container', 'object', until=now
273
        )
274
        self.assertRaises(
275
            NameError,
276
            self.backend.get_public,
277
            '$$account$$',
278
            public
279
        )
280

  
281
if __name__ == '__main__':
282
    unittest.main()
b/snf-pithos-app/pithos/api/util.py
55 55
    Fault, NotModified, BadRequest, Unauthorized, Forbidden, ItemNotFound,
56 56
    Conflict, LengthRequired, PreconditionFailed, RequestEntityTooLarge,
57 57
    RangeNotSatisfiable, InternalServerError, NotImplemented)
58
from pithos.api.short_url import encode_url
59 58
from pithos.api.settings import (BACKEND_DB_MODULE, BACKEND_DB_CONNECTION,
60 59
                                 BACKEND_BLOCK_MODULE, BACKEND_BLOCK_PATH,
61 60
                                 BACKEND_BLOCK_UMASK,
......
68 67
                                 AUTHENTICATION_URL, AUTHENTICATION_USERS,
69 68
                                 COOKIE_NAME, USER_CATALOG_URL,
70 69
                                 RADOS_STORAGE, RADOS_POOL_BLOCKS,
71
                                 RADOS_POOL_MAPS, TRANSLATE_UUIDS)
70
                                 RADOS_POOL_MAPS, TRANSLATE_UUIDS,
71
                                 PUBLIC_URL_MIN_LENGTH,
72
                                 PUBLIC_URL_ALPHABET)
72 73
from pithos.backends import connect_backend
73 74
from pithos.backends.base import (NotAllowedError, QuotaError, ItemNotExists,
74 75
                                  VersionNotExists)
......
387 388
def update_public_meta(public, meta):
388 389
    if not public:
389 390
        return
390
    meta['X-Object-Public'] = '/public/' + encode_url(public)
391
    meta['X-Object-Public'] = '/public/' + public
391 392

  
392 393

  
393 394
def validate_modification_preconditions(request, meta):
......
983 984
        quotaholder_token=QUOTAHOLDER_TOKEN,
984 985
        quotaholder_client_poolsize=QUOTAHOLDER_POOLSIZE,
985 986
        free_versioning=BACKEND_FREE_VERSIONING,
986
        block_params=BLOCK_PARAMS)
987
        block_params=BLOCK_PARAMS,
988
        public_url_min_length=PUBLIC_URL_MIN_LENGTH,
989
        public_url_alphabet=PUBLIC_URL_ALPHABET)
987 990

  
988 991
def get_backend():
989 992
    backend = _pithos_backend_pool.pool_get()
b/snf-pithos-backend/pithos/backends/lib/sqlalchemy/alembic/versions/27381099d477_alter_public_add_col.py
1
"""alter public add column url
2

  
3
Revision ID: 27381099d477
4
Revises: 2a309a9a3438
5
Create Date: 2013-03-20 16:14:20.058077
6

  
7
"""
8

  
9
# revision identifiers, used by Alembic.
10
revision = '27381099d477'
11
down_revision = '2a309a9a3438'
12

  
13
from alembic import op
14
import sqlalchemy as sa
15

  
16
from pithos.backends.modular import ULTIMATE_ANSWER
17
from pithos.api.short_url import encode_url
18

  
19
def upgrade():
20
    op.add_column('public', sa.Column('url', sa.String(2048)))
21
    op.create_unique_constraint('idx_public_url', 'public', ['url'])
22

  
23
    # migrate old rows
24
    p = sa.sql.table(
25
        'public',
26
        sa.sql.column('public_id', sa.Integer),
27
        sa.sql.column('url', sa.String),
28
    )
29
    get_url = lambda x: encode_url(x + ULTIMATE_ANSWER)
30
    conn = op.get_bind()
31
    s = sa.select([p.c.public_id])
32
    rows = conn.execute(s).fetchall()
33
    for r in rows:
34
        s = p.update().values(url=get_url(r[0])).where(p.c.public_id==r[0])
35
        op.execute(s)
36

  
37
def downgrade():
38
    op.drop_constraint('idx_public_url', 'public')
39
    op.drop_column('public', 'url')
b/snf-pithos-backend/pithos/backends/lib/sqlalchemy/public.py
37 37
from sqlalchemy.schema import Index
38 38
from sqlalchemy.exc import NoSuchTableError
39 39

  
40
from pithos.backends.random_word import get_word
40 41

  
41 42
def create_tables(engine):
42 43
    metadata = MetaData()
......
44 45
    columns.append(Column('public_id', Integer, primary_key=True))
45 46
    columns.append(Column('path', String(2048), nullable=False))
46 47
    columns.append(Column('active', Boolean, nullable=False, default=True))
48
    columns.append(Column('url', String(2048), nullable=True))
47 49
    public = Table('public', metadata, *columns, mysql_engine='InnoDB',
48 50
                   sqlite_autoincrement=True)
49 51
    # place an index on path
50 52
    Index('idx_public_path', public.c.path, unique=True)
53
    # place an index on url
54
    Index('idx_public_url', public.c.url, unique=True)
51 55
    metadata.create_all(engine)
52 56
    return metadata.sorted_tables
53 57

  
......
64 68
            tables = create_tables(self.engine)
65 69
            map(lambda t: self.__setattr__(t.name, t), tables)
66 70

  
67
    def public_set(self, path):
71
    def get_unique_url(self, serial, public_url_min_length, public_url_alphabet):
72
        l = public_url_min_length
73
        while 1:
74
            candidate = get_word(serial, length=l, alphabet=public_url_alphabet)
75
            if self.public_path(candidate) is None:
76
                return candidate
77
            l +=1
78

  
79
    def public_set(self, path, public_url_min_length, public_url_alphabet):
68 80
        s = select([self.public.c.public_id])
69 81
        s = s.where(self.public.c.path == path)
70 82
        r = self.conn.execute(s)
71 83
        row = r.fetchone()
72 84
        r.close()
73
        if row:
74
            s = self.public.update().where(self.public.c.public_id == row[0])
75
            s = s.values(active=True)
76
        else:
85

  
86
        if not row:
77 87
            s = self.public.insert()
78 88
            s = s.values(path=path, active=True)
79
        r = self.conn.execute(s)
80
        r.close()
89
            r = self.conn.execute(s)
90
            serial = r.inserted_primary_key[0]
91
            r.close()
92

  
93
            url = self.get_unique_url(
94
                serial, public_url_min_length, public_url_alphabet
95
            )
96
            s = self.public.update().where(self.public.c.public_id == serial)
97
            s = s.values(url=url)
98
            self.conn.execute(s).close()
81 99

  
82 100
    def public_unset(self, path):
83
        s = self.public.update()
101
        s = self.public.delete()
84 102
        s = s.where(self.public.c.path == path)
85
        s = s.values(active=False)
86
        r = self.conn.execute(s)
87
        r.close()
103
        self.conn.execute(s).close()
88 104

  
89 105
    def public_unset_bulk(self, paths):
90 106
        if not paths:
91 107
            return
92
        s = self.public.update()
108
        s = self.public.delete()
93 109
        s = s.where(self.public.c.path.in_(paths))
94
        s = s.values(active=False)
95
        r = self.conn.execute(s)
96
        r.close()
110
        self.conn.execute(s).close()
97 111

  
98 112
    def public_get(self, path):
99
        s = select([self.public.c.public_id])
113
        s = select([self.public.c.url])
100 114
        s = s.where(and_(self.public.c.path == path,
101 115
                         self.public.c.active == True))
102 116
        r = self.conn.execute(s)
......
107 121
        return None
108 122

  
109 123
    def public_list(self, prefix):
110
        s = select([self.public.c.path, self.public.c.public_id])
124
        s = select([self.public.c.path, self.public.c.url])
111 125
        s = s.where(self.public.c.path.like(
112 126
            self.escape_like(prefix) + '%', escape='\\'))
113 127
        s = s.where(self.public.c.active == True)
......
118 132

  
119 133
    def public_path(self, public):
120 134
        s = select([self.public.c.path])
121
        s = s.where(and_(self.public.c.public_id == public,
135
        s = s.where(and_(self.public.c.url == public,
122 136
                         self.public.c.active == True))
123 137
        r = self.conn.execute(s)
124 138
        row = r.fetchone()
b/snf-pithos-backend/pithos/backends/lib/sqlite/public.py
33 33

  
34 34
from dbworker import DBWorker
35 35

  
36
from pithos.backends.random_word import get_word
36 37

  
37 38
class Public(DBWorker):
38 39
    """Paths can be marked as public."""
......
44 45
        execute(""" create table if not exists public
45 46
                          ( public_id integer primary key autoincrement,
46 47
                            path      text not null,
47
                            active    boolean not null default 1 ) """)
48
                            active    boolean not null default 1,
49
                            url       text) """)
48 50
        execute(""" create unique index if not exists idx_public_path
49 51
                    on public(path) """)
52
        execute(""" create unique index if not exists idx_public_url
53
                    on public(url) """)
50 54

  
51
    def public_set(self, path):
52
        q = "insert or ignore into public (path) values (?)"
53
        self.execute(q, (path,))
54
        q = "update public set active = 1 where path = ?"
55
    def get_unique_url(self, serial, public_url_min_length, public_url_alphabet):
56
        l = public_url_min_length
57
        while 1:
58
            candidate = get_word(serial, length=l, alphabet=public_url_alphabet)
59
            if self.public_path(candidate) is None:
60
                return candidate
61
            l +=1
62

  
63
    def public_set(self, path, public_url_min_length, public_url_alphabet):
64
        q = "select public_id from public where path = ?"
55 65
        self.execute(q, (path,))
66
        row = self.fetchone()
67

  
68
        if not row:
69
            q = "insert into public(path, active) values(?, ?)"
70
            serial = self.execute(q, (path, active)).lastrowid
71
            url = self.get_unique_url(
72
                serial, public_url_min_length, public_url_alphabet
73
            )
74
            q = "update public set url=url where public_id = ?"
75
            self.execute(q, (serial,))
56 76

  
57 77
    def public_unset(self, path):
58
        q = "update public set active = 0 where path = ?"
78
        q = "delete from public where path = ?"
59 79
        self.execute(q, (path,))
60 80

  
61 81
    def public_unset_bulk(self, paths):
62 82
        placeholders = ','.join('?' for path in paths)
63
        q = "update public set active = 0 where path in (%s)" % placeholders
83
        q = "delete from public where path in (%s)"
64 84
        self.execute(q, paths)
65 85

  
66 86
    def public_get(self, path):
67
        q = "select public_id from public where path = ? and active = 1"
87
        q = "select url from public where path = ? and active = 1"
68 88
        self.execute(q, (path,))
69 89
        row = self.fetchone()
70 90
        if row:
......
72 92
        return None
73 93

  
74 94
    def public_list(self, prefix):
75
        q = "select path, public_id from public where path like ? escape '\\' and active = 1"
95
        q = "select path, url from public where path like ? escape '\\' and active = 1"
76 96
        self.execute(q, (self.escape_like(prefix) + '%',))
77 97
        return self.fetchall()
78 98

  
79 99
    def public_path(self, public):
80
        q = "select path from public where public_id = ? and active = 1"
100
        q = "select path from public where url = ? and active = 1"
81 101
        self.execute(q, (public,))
82 102
        row = self.fetchone()
83 103
        if row:
b/snf-pithos-backend/pithos/backends/modular.py
84 84
DEFAULT_BLOCK_PARAMS = { 'mappool': None, 'blockpool': None }
85 85
#DEFAULT_QUEUE_HOSTS = '[amqp://guest:guest@localhost:5672]'
86 86
#DEFAULT_QUEUE_EXCHANGE = 'pithos'
87
DEFAULT_ALPHABET = ('0123456789'
88
                    'abcdefghijklmnopqrstuvwxyz'
89
                    'ABCDEFGHIJKLMNOPQRSTUVWXYZ')
90
DEFAULT_MIN_LENGTH = 8
87 91

  
88 92
QUEUE_MESSAGE_KEY_PREFIX = 'pithos.%s'
89 93
QUEUE_CLIENT_ID = 'pithos'
......
148 152
                 quotaholder_enabled=False,
149 153
                 quotaholder_url=None, quotaholder_token=None,
150 154
                 quotaholder_client_poolsize=None,
151
                 free_versioning=True, block_params=None):
155
                 free_versioning=True, block_params=None,
156
                 public_url_min_length=None,
157
                 public_url_alphabet=None):
152 158
        db_module = db_module or DEFAULT_DB_MODULE
153 159
        db_connection = db_connection or DEFAULT_DB_CONNECTION
154 160
        block_module = block_module or DEFAULT_BLOCK_MODULE
......
161 167
        #queue_hosts = queue_hosts or DEFAULT_QUEUE_HOSTS
162 168
        #queue_exchange = queue_exchange or DEFAULT_QUEUE_EXCHANGE
163 169

  
170
        self.public_url_min_length = public_url_min_length or DEFAULT_MIN_LENGTH
171
        self.public_url_alphabet = public_url_alphabet or DEFAULT_ALPHABET
172

  
164 173
        self.hash_algorithm = 'sha256'
165 174
        self.block_size = 4 * 1024 * 1024  # 4MB
166 175
        self.free_versioning = free_versioning
......
718 727
                     account, container, prefix)
719 728
        public = {}
720 729
        for path, p in self.permissions.public_list('/'.join((account, container, prefix))):
721
            public[path] = p + ULTIMATE_ANSWER
730
            public[path] = p
722 731
        return public
723 732

  
724 733
    @backend_method
......
815 824
        self._can_read(user, account, container, name)
816 825
        path = self._lookup_object(account, container, name)[0]
817 826
        p = self.permissions.public_get(path)
818
        if p is not None:
819
            p += ULTIMATE_ANSWER
820 827
        return p
821 828

  
822 829
    @backend_method
......
830 837
        if not public:
831 838
            self.permissions.public_unset(path)
832 839
        else:
833
            self.permissions.public_set(path)
840
            self.permissions.public_set(
841
                path, self.public_url_min_length, self.public_url_alphabet
842
            )
834 843

  
835 844
    @backend_method
836 845
    def get_object_hashmap(self, user, account, container, name, version=None):
......
1097 1106
        """Return the (account, container, name) for the public id given."""
1098 1107

  
1099 1108
        logger.debug("get_public: %s %s", user, public)
1100
        if public is None or public < ULTIMATE_ANSWER:
1101
            raise NameError
1102
        path = self.permissions.public_path(public - ULTIMATE_ANSWER)
1109
        path = self.permissions.public_path(public)
1103 1110
        if path is None:
1104 1111
            raise NameError
1105 1112
        account, container, name = path.split('/', 2)
b/snf-pithos-backend/pithos/backends/random_word.py
1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

  
34
DEFAULT_ALPHABET = ('0123456789'
35
                    'abcdefghijklmnopqrstuvwxyz'
36
                    'ABCDEFGHIJKLMNOPQRSTUVWXYZ')
37
DEFAULT_MIN_LENGTH = 8
38

  
39
from  random import randrange
40
from math import log
41

  
42
def get_random_word(length=DEFAULT_MIN_LENGTH, alphabet=DEFAULT_ALPHABET):
43
    alphabet_length = len(alphabet)
44
    word = ''.join(alphabet[randrange(alphabet_length)]
45
        for _ in xrange(length))
46
    return word
47

  
48
def encode_serial(serial, alphabet=DEFAULT_ALPHABET):
49
    i = 0
50
    base = len(alphabet)
51
    quotient = int(serial)
52
    digits = []
53
    append = digits.append
54
    while quotient > 0:
55
        quotient, remainder = divmod(quotient, base)
56
        append(alphabet[remainder])
57
    word = ''.join(reversed(digits))
58
    return word
59

  
60
def get_word(serial, length=DEFAULT_MIN_LENGTH, alphabet=DEFAULT_ALPHABET):
61
    word = encode_serial(serial, alphabet)
62
    word += get_random_word(length, alphabet)
63
    return word
b/snf-pithos-backend/pithos/backends/util.py
48 48
                 quotaholder_enabled=True,
49 49
                 quotaholder_url=None, quotaholder_token=None,
50 50
                 quotaholder_client_poolsize=None,
51
                 block_params=None):
51
                 block_params=None,
52
                 public_url_min_length=None,
53
                 public_url_alphabet=None):
52 54
        super(PithosBackendPool, self).__init__(size=size)
53 55
        self.db_module = db_module
54 56
        self.db_connection = db_connection
......
64 66
        self.quotaholder_token = quotaholder_token
65 67
        self.quotaholder_client_poolsize = quotaholder_client_poolsize
66 68
        self.free_versioning = free_versioning
69
        self.public_url_min_length=public_url_min_length
70
        self.public_url_alphabet=public_url_alphabet
67 71

  
68 72
    def _pool_create(self):
69 73
        backend = connect_backend(
......
80 84
                quotaholder_url=self.quotaholder_url,
81 85
                quotaholder_token=self.quotaholder_token,
82 86
                quotaholder_client_poolsize=self.quotaholder_client_poolsize,
83
                free_versioning=self.free_versioning)
87
                free_versioning=self.free_versioning,
88
                public_url_min_length=self.public_url_min_length,
89
                public_url_alphabet=self.public_url_alphabet)
84 90

  
85 91
        backend._real_close = backend.close
86 92
        backend.close = instancemethod(_pooled_backend_close, backend,

Also available in: Unified diff