Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*pyc
*egg-info
*egg
19 changes: 13 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
**django-sqlcipher**
# django-sqlcipher

SQLCipher is an SQLite extension that provides transparent 256-bit AES encryption of database files.

Expand All @@ -8,33 +8,40 @@ This app does it for you. You only need to specify the database key in your proj

For more about SQLCipher take a look at [http://sqlcipher.net/](http://sqlcipher.net/).

**Requirements**
## Requirements

* python-sqlcipher (Python compiled with SQLCipher support)

For more about python-sqlcipher take a look at:

[https://code.launchpad.net/~jplacerda/+junk/python-sqlcipher](https://code.launchpad.net/~jplacerda/+junk/python-sqlcipher)

**Installation**
## Installation

`pip install git+http://github.com/codasus/django-sqlcipher#egg=sqlcipher`

Or manually place it on your `PYTHON_PATH`.

**Configuration**
## Configuration

Open your project's `settings.py` file and:

1. Append `sqlcipher` to your `INSTALLED_APPS`.

2. Set your database engine to `sqlcipher.backend`.

3. Put the following line where you want:
3. Optionally, type your key into your settings file (**unsafe**):

`PRAGMA_KEY = "YOUR DATABASE KEY"`

**MIT License**
You may not wish to expose your encryption key in a file.
django-sqlcipher ships with custom management commands that will prompt
for the key when invoking `runserver` and `migrate`, however you
should consider how you want to set `django.conf.settings.PRAGMA_KEY`
at runtime in your production environment.


## MIT License

<pre>Copyright (c) 2011 Caio Ariede and Codasus Technologies.

Expand Down
9 changes: 5 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from distutils.core import setup
from distutils.command.install import INSTALL_SCHEMES
import os

from distutils.command.install import INSTALL_SCHEMES
from setuptools import setup, find_packages

root = os.path.dirname(os.path.abspath(__file__))
os.chdir(root)

VERSION = '0.1.1'
VERSION = '0.1.2'

# Make data go to the right place.
# http://groups.google.com/group/comp.lang.python/browse_thread/thread/35ec7b2fed36eaec/2105ee4d9e8042cb
Expand All @@ -23,7 +24,7 @@
url="http://github.com/codasus/django-sqlcipher",
license="Creative Commons Attribution-ShareAlike 3.0 Unported License",
platforms=["any"],
packages=['sqlcipher'],
packages=find_packages(),
classifiers=[
"Development Status :: 3 - Alpha",
"Framework :: Django",
Expand Down
121 changes: 116 additions & 5 deletions sqlcipher/backend/base.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,121 @@
from django.db.backends.sqlite3.base import DatabaseWrapper as BaseDatabaseWrapper
from __future__ import unicode_literals

from django.db.backends.sqlite3.base import DatabaseWrapper as BaseDatabaseWrapper, \
_sqlite_date_extract, _sqlite_date_trunc, _sqlite_datetime_cast_date, \
_sqlite_datetime_extract, _sqlite_datetime_trunc, _sqlite_time_extract, \
_sqlite_regexp, _sqlite_format_dtdelta, _sqlite_power, FORMAT_QMARK_REGEX

from ..signals import setup

from pysqlcipher import dbapi2 as Database


import datetime
import decimal
import warnings

from django.conf import settings
from django.db.backends import utils as backend_utils
from django.utils import six, timezone
from django.utils.dateparse import (
parse_date, parse_datetime, parse_time,
)
from django.utils.deprecation import RemovedInDjango20Warning
from django.utils.safestring import SafeBytes

try:
import pytz
except ImportError:
pytz = None

DatabaseError = Database.DatabaseError
IntegrityError = Database.IntegrityError


def adapt_datetime_warn_on_aware_datetime(value):
# Remove this function and rely on the default adapter in Django 2.0.
if settings.USE_TZ and timezone.is_aware(value):
warnings.warn(
"The SQLite database adapter received an aware datetime (%s), "
"probably from cursor.execute(). Update your code to pass a "
"naive datetime in the database connection's time zone (UTC by "
"default).", RemovedInDjango20Warning)
# This doesn't account for the database connection's timezone,
# which isn't known. (That's why this adapter is deprecated.)
value = value.astimezone(timezone.utc).replace(tzinfo=None)
return value.isoformat(str(" "))


def decoder(conv_func):
""" The Python sqlite3 interface returns always byte strings.
This function converts the received value to a regular string before
passing it to the receiver function.
"""
return lambda s: conv_func(s.decode('utf-8'))


Database.register_converter(str("bool"), decoder(lambda s: s == '1'))
Database.register_converter(str("time"), decoder(parse_time))
Database.register_converter(str("date"), decoder(parse_date))
Database.register_converter(str("datetime"), decoder(parse_datetime))
Database.register_converter(str("timestamp"), decoder(parse_datetime))
Database.register_converter(str("TIMESTAMP"), decoder(parse_datetime))
Database.register_converter(str("decimal"), decoder(backend_utils.typecast_decimal))

Database.register_adapter(datetime.datetime, adapt_datetime_warn_on_aware_datetime)
Database.register_adapter(decimal.Decimal, backend_utils.rev_typecast_decimal)
if six.PY2:
Database.register_adapter(str, lambda s: s.decode('utf-8'))
Database.register_adapter(SafeBytes, lambda s: s.decode('utf-8'))


class DatabaseWrapper(BaseDatabaseWrapper):
def _cursor(self):
if self.connection is None:
setup()
return super(DatabaseWrapper, self)._cursor()
Database = Database

def create_cursor(self, name=None):
if name:
base_cursor = super(DatabaseWrapper, self).create_cursor(name)
else:
base_cursor = super(DatabaseWrapper, self).create_cursor()
return SQLiteCursorWrapper(base_cursor)

# def _cursor(self, *args, **kwargs):
# if self.connection is None:
# setup()
# return super(DatabaseWrapper, self)._cursor(*args, **kwargs)

def get_new_connection(self, conn_params):
conn = Database.connect(**conn_params)
conn.create_function("django_date_extract", 2, _sqlite_date_extract)
conn.create_function("django_date_trunc", 2, _sqlite_date_trunc)
conn.create_function("django_datetime_cast_date", 2, _sqlite_datetime_cast_date)
conn.create_function("django_datetime_extract", 3, _sqlite_datetime_extract)
conn.create_function("django_datetime_trunc", 3, _sqlite_datetime_trunc)
conn.create_function("django_time_extract", 2, _sqlite_time_extract)
conn.create_function("regexp", 2, _sqlite_regexp)
conn.create_function("django_format_dtdelta", 3, _sqlite_format_dtdelta)
conn.create_function("django_power", 2, _sqlite_power)
return conn

def create_cursor(self, *args, **kwargs):
return self.connection.cursor(factory=SQLiteCursorWrapper)


class SQLiteCursorWrapper(Database.Cursor):
"""
Django uses "format" style placeholders, but pysqlite2 uses "qmark" style.
This fixes it -- but note that if you want to use a literal "%s" in a query,
you'll need to use "%%s".
"""
def execute(self, query, params=None):
if params is None:
return Database.Cursor.execute(self, query)
query = self.convert_query(query)
return Database.Cursor.execute(self, query, params)

def executemany(self, query, param_list):
query = self.convert_query(query)
return Database.Cursor.executemany(self, query, param_list)

def convert_query(self, query):
return FORMAT_QMARK_REGEX.sub('?', query).replace('%%', '%')
Empty file.
Empty file.
17 changes: 17 additions & 0 deletions sqlcipher/management/commands/_mixins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from __future__ import print_function


from sqlcipher.utils import ensure_pragma_key


class PromptForPragmaKeyMixin(object):
"""""
This is a universal command that you can have other management commands
inherit from in case they need database access.
"""

def handle(self, *args, **options):
ensure_pragma_key()
super(PromptForPragmaKeyMixin, self).handle(*args, **options)
6 changes: 6 additions & 0 deletions sqlcipher/management/commands/dumpdata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.core.management.commands.dumpdata import Command as DumpdataCommand

from ._mixins import PromptForPragmaKeyMixin

class Command(PromptForPragmaKeyMixin, DumpdataCommand):
pass
6 changes: 6 additions & 0 deletions sqlcipher/management/commands/loaddata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.core.management.commands.loaddata import Command as LoaddataCommand

from ._mixins import PromptForPragmaKeyMixin

class Command(PromptForPragmaKeyMixin, LoaddataCommand):
pass
12 changes: 12 additions & 0 deletions sqlcipher/management/commands/migrate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

from django.core.management.commands.migrate import Command as BaseCommand

from ._mixins import PromptForPragmaKeyMixin


class Command(PromptForPragmaKeyMixin, BaseCommand):
"""
Before migrating, we need to know the pragma key to access the database. If
it does not exist, retrieve it from command line input.
"""
pass
10 changes: 10 additions & 0 deletions sqlcipher/management/commands/runserver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.core.management.commands.runserver import Command as RunserverCommand

from ._mixins import ensure_pragma_key


class Command(RunserverCommand):

def inner_run(self, *args, **options):
ensure_pragma_key()
RunserverCommand.inner_run(self, *args, **options)
2 changes: 2 additions & 0 deletions sqlcipher/signals.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import unicode_literals

from django.conf import settings
from django.db.backends.signals import connection_created

Expand Down
15 changes: 15 additions & 0 deletions sqlcipher/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from __future__ import print_function

import sys

from django.conf import settings
from getpass import getpass


def ensure_pragma_key():
if not hasattr(settings, 'PRAGMA_KEY') or not settings.PRAGMA_KEY:
sys.stderr.write("There is no SQL Cipher key defined, it's unsafe to store in your settings. Please input your key.\n\n")
key = getpass("Key: ")
settings.PRAGMA_KEY = key.decode("utf-8")