├── django_php_bridge ├── backends │ ├── __init__.py │ └── db.py └── __init__.py ├── AUTHORS ├── MANIFEST.in ├── contrib ├── mysql │ └── create_django_session.sql ├── apache2 │ └── vhost_conf └── php │ ├── user.class.php │ └── djangoSession.class.php ├── setup.py ├── LICENSE └── README.rst /django_php_bridge/backends/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Wes Winham 2 | Jon Dufresne 3 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS 2 | include LICENSE 3 | include MANIFEST.in 4 | include README.rst 5 | include setup.py 6 | recursive-include django_php_bridge *.py 7 | recursive-include contrib * 8 | prune django_php_bridge/*.pyc 9 | -------------------------------------------------------------------------------- /contrib/mysql/create_django_session.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | CREATE TABLE "django_session" ( 3 | "session_key" varchar(40) NOT NULL PRIMARY KEY, 4 | "session_data" text NOT NULL, 5 | "expire_date" datetime NOT NULL 6 | ) 7 | ; 8 | COMMIT; 9 | -------------------------------------------------------------------------------- /django_php_bridge/__init__.py: -------------------------------------------------------------------------------- 1 | """Authentication bridge between Django and PHP""" 2 | 3 | VERSION = (0, 1, 1, '') 4 | 5 | __version__ = ".".join(map(str, VERSION[:-1])) 6 | __release__ = ".".join(map(str, VERSION)) 7 | __author__ = "Wes Winham" 8 | __contact__ = "winhamwr@gmail.com" 9 | __homepage__ = "http://github.com/winhamwr/django-php-bridge/" 10 | __docformat__ = "restructuredtext" 11 | -------------------------------------------------------------------------------- /django_php_bridge/backends/db.py: -------------------------------------------------------------------------------- 1 | import phpserialize 2 | 3 | from django.contrib.sessions.backends.db import SessionStore as DbStore 4 | 5 | class SessionStore(DbStore): 6 | ''' 7 | Thanks to Alex Ezell for assistance on this php-django session engine. 8 | http://groups.google.com/group/django-users/browse_thread/thread/f5b464379f2e4154/e358161c95e507c0 9 | 10 | Override the default database session backend to use php-style serialization. 11 | ''' 12 | def __init__(self, session_key=None): 13 | # call the super class's init 14 | super(SessionStore, self).__init__(session_key) 15 | 16 | def decode(self, session_data): 17 | return phpserialize.loads(session_data) 18 | 19 | def encode(self, session_dict): 20 | return phpserialize.dumps(session_dict) 21 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import codecs 4 | 5 | from setuptools import setup 6 | 7 | import django_php_bridge 8 | long_description = codecs.open("README.rst", "r", "utf-8").read() 9 | 10 | CLASSIFIERS = [ 11 | 'Development Status :: 3 - Alpha', 12 | 'Intended Audience :: Developers', 13 | 'License :: OSI Approved :: BSD License', 14 | 'Operating System :: OS Independent', 15 | 'Programming Language :: Python', 16 | 'Topic :: Software Development :: Libraries :: Python Modules', 17 | 'Framework :: Django', 18 | ] 19 | 20 | setup( 21 | name='django-php-bridge', 22 | version=django_php_bridge.__version__, 23 | description=django_php_bridge.__doc__, 24 | author=django_php_bridge.__author__, 25 | author_email=django_php_bridge.__contact__, 26 | url=django_php_bridge.__homepage__, 27 | long_description=long_description, 28 | packages=['django_php_bridge'], 29 | license='BSD', 30 | platforms=['any'], 31 | classifiers=CLASSIFIERS, 32 | install_requires=['phpserialize==1.3'], 33 | ) 34 | -------------------------------------------------------------------------------- /contrib/apache2/vhost_conf: -------------------------------------------------------------------------------- 1 | # Example apache2 virtual host file for hosting a Django and a PHP project 2 | # side by side and integrating them using Django-PHP-Bridge 3 | # Wes Winham 4 | # http://github.com/winhamwr/django-php-bridge 5 | 6 | # Assumes that static media is being handled by something like Nginx elsewhere 7 | # Assumes mod_php or similar is installed and configured 8 | 9 | # Variables that need python string template replacing: 10 | # domain_name 11 | # project_name 12 | # project_username 13 | # project_root_path 14 | # wsgi_file_path 15 | 16 | NameVirtualHost *:80 17 | 18 | 19 | ServerAdmin webmaster@%(domain_name)s 20 | ServerName %(domain_name)s 21 | DocumentRoot %(project_root_path)s 22 | 23 | WSGIDaemonProcess %(project_name)s user=%(project_username)s processes=2 threads=10 24 | WSGIProcessGroup %(project_name)s 25 | 26 | WSGIScriptAlias / %(wsgi_file_path)s 27 | 28 | # Configure all PHP-served URLs here 29 | Alias /account/ %(project_root_path)s/account/ 30 | Alias /class/ %(project_root_path)s/class/ 31 | 32 | 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 PolicyStat LLC 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /contrib/php/user.class.php: -------------------------------------------------------------------------------- 1 | id = $user_id; 19 | $this->load(); 20 | } 21 | } 22 | 23 | private function load(){ 24 | $sql = "SELECT auth_user.id as 'id', username, first_name, last_name, 25 | email, password, is_staff, is_active, is_superuser, last_login, 26 | date_joined 27 | FROM auth_user 28 | WHERE auth_user.id = '$this->id'"; 29 | 30 | $result = Database::runQuery($sql); 31 | while($row = mysql_fetch_assoc($result)){ 32 | foreach($row as $key => $value){ 33 | $this->$key = $value; 34 | } 35 | } 36 | } 37 | 38 | /** 39 | * Takes a salt and a password and returns the hashed password. 40 | * @param salt 41 | * @param password 42 | */ 43 | public static function hashPassword($salt, $password){ 44 | return 'sha1$'.$salt.'$'.sha1($salt.$password); 45 | } 46 | 47 | /** 48 | * Get the password hash from the django-formatted password column. 49 | * @param fullHash String in format Format: sha1$salt$hash 50 | */ 51 | public static function getPasswordHash($fullHash){ 52 | $hashStart = strripos($fullHash, '$'); 53 | return substr($fullHash, $hashStart + 1); 54 | } 55 | } 56 | ?> 57 | -------------------------------------------------------------------------------- /contrib/php/djangoSession.class.php: -------------------------------------------------------------------------------- 1 | 0){ 56 | $record = mysql_fetch_assoc($result); 57 | return $record['session_data']; 58 | }else{ 59 | return ''; 60 | } 61 | }else{ 62 | return ''; 63 | } 64 | 65 | } 66 | 67 | /** 68 | * Write the new data in to the session. 69 | * @param session_key 70 | * @param session_data 71 | */ 72 | public static function write($session_key, $session_data){ 73 | //Unix timestamp expiration using the php session expiration config 74 | $expire_date = time() + TWO_WEEKS; 75 | //Convert to a mysql DATETIME value 76 | $expire_date = date('Y-m-d H:i:s', $expire_date); 77 | 78 | $session_key = mysql_real_escape_string($session_key); 79 | //We're keeping expire_date instead of last access 80 | //This departs from how PHP keeps sessions, but jives with Django 81 | $expire_date = mysql_real_escape_string($expire_date); 82 | $session_data = mysql_real_escape_string($session_data); 83 | 84 | $sql = "SELECT session_key 85 | FROM django_session 86 | WHERE session_key = '$session_key'"; 87 | $result = Database::runQuery($sql); 88 | $num = mysql_num_rows($result); 89 | if($num ==1){ 90 | $sql = "UPDATE django_session 91 | SET session_data = '$session_data', 92 | expire_date = '$expire_date' 93 | WHERE session_key = '$session_key'"; 94 | }else{ 95 | $sql = "INSERT INTO django_session(session_key, session_data, expire_date) 96 | VALUES('$session_key', '$session_data', '$expire_date')"; 97 | } 98 | 99 | return Database::runQuery($sql); 100 | } 101 | 102 | public static function destroy($session_key){ 103 | $session_key = mysql_real_escape_string($session_key); 104 | $sql = "DELETE 105 | FROM django_session 106 | WHERE session_key = '$session_key'"; 107 | return Database::runQuery($sql); 108 | } 109 | 110 | /** 111 | * Clean out old sessions from the DB. We're ignoring the PHP configuration 112 | * that determines default session length to more-closely match the way Django 113 | * handles session expiration. 114 | * @param max Ignored 115 | */ 116 | public static function clean($max){ 117 | $sql = "DELETE 118 | FROM django_session 119 | WHERE expire_date < NOW()"; 120 | return Database::runQuery($sql); 121 | } 122 | } 123 | ?> 124 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ========================================================= 2 | django-php-bridge - Authentication betwen Django and PHP 3 | ========================================================= 4 | 5 | ******* 6 | Purpose 7 | ******* 8 | 9 | `Django-PHP-Bridge`_ is a Django authentication backend that allows your Django 10 | project to seemlessly pass users to and from a PHP application. This allows 11 | you to build an application with both PHP and Django components while keeping a 12 | solid user experience. 13 | 14 | Whether you're porting from PHP to Django, from Django to PHP, integrating two 15 | distinct applications or building a hybrid app, `Django-PHP-Bridge`_ aims to 16 | help make the distinction irrelevant to your users. 17 | 18 | **************** 19 | General Approach 20 | **************** 21 | 22 | There are several different ways to approach this problem, mostly revolving 23 | around which side's default behavior you use and which side needs more 24 | customization. In general, we've taken (and this documentation assumes) that 25 | Django's default behavior should be used where possible. However, it is 26 | completely possible to use the provided session backend as part of a more 27 | PHP-centric approach. 28 | 29 | Django Defaults Used 30 | ==================== 31 | 32 | * ``django.contrib.auth.models.User`` is used to store Users, with the standard 33 | django Profile extension recommended for additional fields. 34 | * The database is used as the session store, but this could easily be 35 | customized. 36 | 37 | PHP Defaults Used 38 | ================= 39 | 40 | * ``django-php-bridge.backends.db`` uses PHP's native serialization format to 41 | store session data. 42 | 43 | ***** 44 | Usage 45 | ***** 46 | 47 | This usage guide assumes a few things about your setup. 48 | 49 | 1. You're using Django's ``django.contrib.auth.backends.ModelBackend`` as your 50 | authentication backend and you want to use ``django.contrib.auth.models.User`` 51 | for storing basic user information. 52 | 2. Your PHP and Django projects share a database. That's how the session 53 | coordination is accomplished. 54 | 3. If you had a custom schema to store your user and profile information, 55 | you've already converted it to Django's schema. 56 | 57 | Usage: Django Side 58 | ================== 59 | 60 | On the Django side of your project, installation is fairly simple. 61 | 62 | Install `Django-PHP-Bridge`_:: 63 | 64 | $ pip install django-php-bridge 65 | 66 | Configure your Django project to use the PHP-compatible session backend by 67 | adding the following to your ``settings.py`` :: 68 | 69 | SESSION_ENGINE = 'django_php_bridge.backends.db' 70 | SESSION_COOKIE_NAME = 'PHPSESSID' 71 | 72 | Let your Django project know that you'll be using the PHP side of your project 73 | to do actual logins. You do this by setting the ``LOGIN_URL`` setting in 74 | ``settings.py`` to point to the PHP-served URL that will be handling your 75 | login. eg.:: 76 | 77 | LOGIN_URL = '/' 78 | 79 | Usage: PHP Side 80 | =============== 81 | 82 | Installation and setup on the PHP side is complicated by the fact that PHP 83 | applications are all generally very different. A helper/guide for using 84 | Django-PHP-Bridge with common PHP frameworks like `CakePHP`_ and `Symfony`_ 85 | would be easier to write (and would be an appreciated contribution). 86 | 87 | In general, the steps involved are: 88 | 89 | Session serialization 90 | --------------------- 91 | 92 | PHP's session serialization has an incompatible default value. Prior to version 93 | 5.5.4 you can only store simple data without breaking functionalitiy of this library. 94 | This is due to the fact that it doesn't use the `serialize()` and `unserialize()` 95 | functions of PHP. 96 | 97 | If you store a multi-dimensional array in `$_SESSION` you will get an incompatible 98 | serialized array in the session data. This issue will pop up if you for example use Zend 99 | Framework 2's SessionManager. 100 | 101 | The PHP setting `session.serialize_handler`_ has the default set to 'php', which is 102 | an interal serializer. Since version 5.5.4 you are able to set it to 'php_serialize'. 103 | This option will set the serialization of the sessions to PHP's default `serialize()` 104 | and `unserialize()` functions and make it compatible when using multi-dimensional 105 | arrays. 106 | 107 | .. _`session.serialize_handler`: http://nl3.php.net/manual/en/session.configuration.php#ini.session.serialize-handler 108 | 109 | Create and Use a Compatible Session Table 110 | ----------------------------------------- 111 | 112 | The session table you use needs to be compatible with the schema that Django 113 | expects. The exact SQL to create the table will vary, but the Django Docs on 114 | the `sql command`_ show us an easy way to obtain the SQL from your Django 115 | project by running:: 116 | 117 | $ django-admin.py sql sessions 118 | 119 | If you're using MySQL, you can use ``contrib/mysql/django_session_table.sql`` 120 | 121 | Alternatively, you can use Django's syncdb to create the table:: 122 | 123 | $ manage.py syncdb 124 | 125 | .. _`sql command`: http://docs.djangoproject.com/en/dev/ref/django-admin/#sql-appname-appname 126 | 127 | Place the Appropriate Session-Handler on Every Page 128 | --------------------------------------------------- 129 | 130 | PHP allows for `custom session handlers`_ to be defined, which allows us to 131 | use the django_session table we created above. The session handler you use will 132 | need to be aware of the django_session table's schema and you'll need to 133 | register this session handler on every page *before* calling ``session_start();``. 134 | 135 | An example session handler class is provided in 136 | ``contrib/php/djangoSession.class.php``. 137 | 138 | .. _`custom session handlers`: http://php.net/manual/en/session.customhandler.php 139 | 140 | Create and Use a Compatible User Table 141 | -------------------------------------- 142 | 143 | In order for any reasonable level of integration, most projects will need to 144 | know who users are on both the PHP and Django side. Because most general 145 | PHP projects vary greatly in how they store their user information, if coming 146 | from an existing PHP project, this will probably require some custom work to 147 | convert user data. Django applications generally use a User model plus a 148 | Profile model to store user data. See the `Django Auth Documentation`_ for 149 | details. 150 | 151 | Included is an example of a PHP class that relies on the same schema as 152 | ``django.contrib.auth.models.User`` as an example and starting point. It knows 153 | a little bit about how Django stores passwords and what fields are necessary, 154 | but it will certainly need tweaking to work with your existing PHP 155 | project. The file is located at ``contrib/php/user.class.php``. 156 | 157 | Suggestions and contributions to make this part of the integration process 158 | easier are welcome. 159 | 160 | .. _`Django Auth Documentation`: http://docs.djangoproject.com/en/1.3/topics/auth/ 161 | 162 | Configure URLs Handled by PHP vs Django 163 | --------------------------------------- 164 | 165 | The final piece of integration will be to tell your web server how to determine 166 | if a given request should be resolved by the Django side or by the PHP side. 167 | This means changing your configuration so that for example, everything at 168 | ``/account`` is served by Django and everything at ``/blog`` is served by PHP. 169 | If you're using different domains or subdomains to separate the side of your app, 170 | then you can ignore this step. 171 | 172 | Generally, to keep this part sane, you'll want to follow good URL practices and 173 | separate which side of your project handles particular tasks and domain objects. 174 | Django's application-centric ``urls.py`` configuration makes this easy. 175 | Particular attention should be paid with regards to which side of your project 176 | should handle logging in and logging out. It's generally simpler if either 177 | only Django or only PHP handles both logging in and logging out users and 178 | probably simpler if that same side handles registration and account editing. 179 | 180 | In the case of `Apache2`_ running `mod_wsgi`_ for Django and mod_php (or 181 | similar) for PHP, the separation can be accomplished inside a VirtualHost file. 182 | An example vhost file is provided at ``contrib/apache2/vhost_conf``. 183 | 184 | ******* 185 | History 186 | ******* 187 | 188 | This authentication backend was extracted from code used in production by 189 | a saas policy management start-up called `PolicyStat`_ during their multi-year 190 | conversion from a PHP application to a `Django`_ application. You can read 191 | a bit about their `PHP to Django Conversion`_. 192 | 193 | `PolicyStat`_ has since converted to 100% Django and is no longer using this 194 | approach in production, but the hope is that someone who is will be interested 195 | in taking an active role in this project. 196 | 197 | ************ 198 | Contributing 199 | ************ 200 | 201 | All development on Django-PHP-Bridge happens at Github: http://github.com/winhamwr/django-php-bridge 202 | 203 | You are highly encourage to contribute to the improvement of Django-PHP-Bridge. 204 | We would especially love contributions along the lines of how to integrate with 205 | specific PHP frameworks. 206 | 207 | *********** 208 | Bug Tracker 209 | *********** 210 | 211 | If you have any suggestions, bug reports or questions please report them 212 | to our issue tracker at http://github.com/winhamwr/django-php-bridge/issues/ 213 | 214 | Also feel free to tweet @weswinham on twitter. 215 | 216 | 217 | .. For full documenation, you can build the `sphinx`_ documentation yourself or 218 | .. vist the `online Django-PHP-Bridge documentation`_ 219 | 220 | .. _`Django-PHP-Bridge`: http://github.com/winhamwr/django-php-bridge/ 221 | .. _`Policystat`: http://policystat.com 222 | .. _`Django`: http://www.djangoproject.com/ 223 | .. _`CakePHP`: http://cakephp.org/ 224 | .. _`Symfony`: http://www.symfony-project.org/ 225 | .. _`Apache2`: http://httpd.apache.org/ 226 | .. _`mod_wsgi`: http://www.modwsgi.org/ 227 | .. _`PHP to Django Conversion`: http://devblog.policystat.com/php-to-django-changing-the-engine-while-the-c 228 | .. _`sphinx`: http://sphinx.pocoo.org/ 229 | .. _`online Django-PHP-Bridge documentation`: http://readthedocs.org/projects/django-php-bridge/ 230 | 231 | --------------------------------------------------------------------------------