Ticket #10300: S3Storage.py

File S3Storage.py, 6.1 KB (added by ErikW, 10 years ago)

Modified S3Storage.py to include Encrypt content.

Line 
1import os
2from mimetypes import guess_type
3
4try:
5    from cStringIO import StringIO
6except ImportError:
7    from StringIO import StringIO
8
9from django.conf import settings
10from django.core.exceptions import ImproperlyConfigured
11from django.core.files.base import File
12from django.core.files.storage import Storage
13from django.utils.functional import curry
14import ezPyCrypto
15
16ACCESS_KEY_NAME = 'AWS_ACCESS_KEY_ID'
17SECRET_KEY_NAME = 'AWS_SECRET_ACCESS_KEY'
18AWS_HEADERS = 'AWS_HEADERS'
19
20#ACCESS_KEY_NAME = settings.AWS_ACCESS_KEY_NAME
21#SECRET_KEY_NAME = settings.AWS_SECRET_KEY_NAME
22#AWS_HEADERS = settings.AWS_HEADERS
23
24try:
25    from S3 import AWSAuthConnection, QueryStringAuthGenerator
26except ImportError:
27    raise ImproperlyConfigured, "Could not load amazon's S3 bindings.\
28    \nSee http://developer.amazonwebservices.com/connect/entry.jspa?externalID=134"
29
30
31class S3Storage(Storage):
32    """Amazon Simple Storage Service"""
33
34    def __init__(self, bucket=settings.AWS_STORAGE_BUCKET_NAME, 
35            access_key=None, secret_key=None, acl='public-read', 
36            calling_format=settings.AWS_CALLING_FORMAT, encrypt=False):
37        self.bucket = bucket
38        self.acl = acl
39        self.encrypt = encrypt
40
41        if not access_key and not secret_key:
42             access_key, secret_key = self._get_access_keys()
43
44        self.connection = AWSAuthConnection(access_key, secret_key, 
45                            calling_format=calling_format)
46        self.generator = QueryStringAuthGenerator(access_key, secret_key, 
47                            calling_format=calling_format, is_secure=False)
48       
49        self.headers = getattr(settings, AWS_HEADERS, {})
50
51    def _get_access_keys(self):
52        access_key = getattr(settings, ACCESS_KEY_NAME, None)
53        secret_key = getattr(settings, SECRET_KEY_NAME, None)
54        if (access_key or secret_key) and (not access_key or not secret_key):
55            access_key = os.environ.get(ACCESS_KEY_NAME)
56            secret_key = os.environ.get(SECRET_KEY_NAME)
57
58        if access_key and secret_key:
59            # Both were provided, so use them
60            return access_key, secret_key
61
62        return None, None
63
64    def _get_connection(self):
65        return AWSAuthConnection(*self._get_access_keys())
66
67    def _put_file(self, name, content):
68        if self.encrypt == True:
69
70            # Create a key object
71            k = ezPyCrypto.key()
72
73            # Read in a public key
74            fd = open(settings.CRYPTO_KEYS_PUB, "rb")
75            pubkey = fd.read()
76            fd.close()
77
78            # import this public key
79            k.importKey(pubkey)
80
81            # Now encrypt some text against this public key
82            content = k.encString(content)
83
84        content_type = guess_type(name)[0] or "application/x-octet-stream"
85        self.headers.update({'x-amz-acl': self.acl, 'Content-Type': content_type})
86        response = self.connection.put(self.bucket, name, content, self.headers)
87
88    def _open(self, name, mode='rb'):
89        remote_file = S3StorageFile(name, self, mode=mode)
90        return remote_file
91
92    def _read(self, name, start_range=None, end_range=None):
93        if start_range is None:
94            headers = {}
95        else:
96            headers = {'Range': 'bytes=%s-%s' % (start_range, end_range)}
97        response = self.connection.get(self.bucket, name, headers)
98        headers = response.http_response.msg
99
100        if self.encrypt == True:
101            # Read in a private key
102            fd = open(settings.CRYPTO_KEYS_PRIV, "rb")
103            pubprivkey = fd.read()
104            fd.close()
105
106            # Create a key object, and auto-import private key
107            k = ezPyCrypto.key(pubprivkey)
108
109            # Decrypt this file
110            response.object.data = k.decString(response.object.data)
111
112        return response.object.data, headers['etag'], headers.get('content-range', None)
113       
114    def _save(self, name, content):
115        content.open()
116        if hasattr(content, 'chunks'):
117            content_str = ''.join(chunk for chunk in content.chunks())
118        else:
119            content_str = content.read()
120        self._put_file(name, content_str)
121        return name
122   
123    def delete(self, name):
124        self.connection.delete(self.bucket, name)
125
126    def exists(self, name):
127        response = self.connection._make_request('HEAD', self.bucket, name)
128        return response.status == 200
129
130    def size(self, name):
131        response = self.connection._make_request('HEAD', self.bucket, name)
132        content_length = response.getheader('Content-Length')
133        return content_length and int(content_length) or 0
134   
135    def url(self, name):
136        return self.generator.make_bare_url(self.bucket, name)
137
138    ## UNCOMMENT BELOW IF NECESSARY
139    #def get_available_name(self, name):
140    #    """ Overwrite existing file with the same name. """
141    #    return name
142
143
144class S3StorageFile(File):
145    def __init__(self, name, storage, mode):
146        self._name = name
147        self._storage = storage
148        self._mode = mode
149        self._is_dirty = False
150        self.file = StringIO()
151        self.start_range = 0
152   
153    @property
154    def size(self):
155        if not hasattr(self, '_size'):
156            self._size = self._storage.size(self._name)
157        return self._size
158
159    def read(self, num_bytes=None):
160        if num_bytes is None:
161            args = []
162            self.start_range = 0
163        else:
164            args = [self.start_range, self.start_range+num_bytes-1]
165        data, etags, content_range = self._storage._read(self._name, *args)
166        if content_range is not None:
167            current_range, size = content_range.split(' ', 1)[1].split('/', 1)
168            start_range, end_range = current_range.split('-', 1)
169            self._size, self.start_range = int(size), int(end_range)+1
170        self.file = StringIO(data)
171        return self.file.getvalue()
172
173    def write(self, content):
174        if 'w' not in self._mode:
175            raise AttributeError("File was opened for read-only access.")
176        self.file = StringIO(content)
177        self._is_dirty = True
178
179    def close(self):
180        if self._is_dirty:
181            self._storage._put_file(self._name, self.file.getvalue())
182        self.file.close()
Back to Top