summaryrefslogtreecommitdiffstats
path: root/mediawiki
diff options
context:
space:
mode:
authorEvgeny Fadeev <evgeny.fadeev@gmail.com>2010-01-24 19:53:24 -0500
committerEvgeny Fadeev <evgeny.fadeev@gmail.com>2010-01-24 20:03:45 -0500
commitad2e22b999b3b795f60e0f95abcaf3b339567294 (patch)
treeebea9e231463d878ff869e4a74ecd4620e743a95 /mediawiki
parentc4da893b2e28dbd2a04f8c6f61c52936119b1148 (diff)
downloadaskbot-ad2e22b999b3b795f60e0f95abcaf3b339567294.tar.gz
askbot-ad2e22b999b3b795f60e0f95abcaf3b339567294.tar.bz2
askbot-ad2e22b999b3b795f60e0f95abcaf3b339567294.zip
recaptcha for conventional registration\n\
simpler email subscription form at registration\n\ fixed urls in rss feed\n\ added experimental remote password login api (cleartext password for remote site entered locally)\n\ included example for Mediawiki Authentication plugin\n\ very simple message to everyone management command
Diffstat (limited to 'mediawiki')
-rw-r--r--mediawiki/PHPSerialize.py149
-rw-r--r--mediawiki/PHPUnserialize.py187
-rw-r--r--mediawiki/README106
-rw-r--r--mediawiki/UserRegister.alias.php7
-rw-r--r--mediawiki/UserRegister.body.php14
-rw-r--r--mediawiki/UserRegister.i18n.php6
-rw-r--r--mediawiki/UserRegister.php24
-rw-r--r--mediawiki/WsgiInjectableSpecialPage.php80
-rw-r--r--mediawiki/__init__.py0
-rw-r--r--mediawiki/api.py138
-rw-r--r--mediawiki/auth.py59
-rw-r--r--mediawiki/forms.py164
-rw-r--r--mediawiki/junk.py2
-rw-r--r--mediawiki/middleware.py57
-rw-r--r--mediawiki/models.py312
-rw-r--r--mediawiki/templatetags/__init__.py0
-rw-r--r--mediawiki/templatetags/mediawikitags.py62
-rw-r--r--mediawiki/views.py192
18 files changed, 1559 insertions, 0 deletions
diff --git a/mediawiki/PHPSerialize.py b/mediawiki/PHPSerialize.py
new file mode 100644
index 00000000..d25b71bd
--- /dev/null
+++ b/mediawiki/PHPSerialize.py
@@ -0,0 +1,149 @@
+import types, string
+
+"""
+Serialize class for the PHP serialization format.
+
+@version v0.4 BETA
+@author Scott Hurring; scott at hurring dot com
+@copyright Copyright (c) 2005 Scott Hurring
+@license http://opensource.org/licenses/gpl-license.php GNU Public License
+$Id: PHPSerialize.py,v 1.1 2006/01/08 21:53:19 shurring Exp $
+
+Most recent version can be found at:
+http://hurring.com/code/python/phpserialize/
+
+Usage:
+# Create an instance of the serialize engine
+s = PHPSerialize()
+# serialize some python data into a string
+serialized_string = s.serialize(string)
+# encode a session list (php's session_encode)
+serialized_string = s.session_encode(list)
+
+See README.txt for more information.
+"""
+
+class PHPSerialize(object):
+ """
+ Class to serialize data using the PHP Serialize format.
+
+ Usage:
+ serialized_string = PHPSerialize().serialize(data)
+ serialized_string = PHPSerialize().session_encode(list)
+ """
+
+ def __init__(self):
+ pass
+
+ def session_encode(self, session):
+ """Thanks to Ken Restivo for suggesting the addition
+ of session_encode
+ """
+ out = ""
+ for (k,v) in session.items():
+ out = out + "%s|%s" % (k, self.serialize(v))
+ return out
+
+ def serialize(self, data):
+ return self.serialize_value(data)
+
+ def is_int(self, data):
+ """
+ Determine if a string var looks like an integer
+ TODO: Make this do what PHP does, instead of a hack
+ """
+ try:
+ int(data)
+ return True
+ except:
+ return False
+
+ def serialize_key(self, data):
+ """
+ Serialize a key, which follows different rules than when
+ serializing values. Many thanks to Todd DeLuca for pointing
+ out that keys are serialized differently than values!
+
+ From http://us2.php.net/manual/en/language.types.array.php
+ A key may be either an integer or a string.
+ If a key is the standard representation of an integer, it will be
+ interpreted as such (i.e. "8" will be interpreted as int 8,
+ while "08" will be interpreted as "08").
+ Floats in key are truncated to integer.
+ """
+ # Integer, Long, Float, Boolean => integer
+ if type(data) is types.IntType or type(data) is types.LongType \
+ or type(data) is types.FloatType or type(data) is types.BooleanType:
+ return "i:%s;" % int(data)
+
+ # String => string or String => int (if string looks like int)
+ elif type(data) is types.StringType:
+ if self.is_int(data):
+ return "i:%s;" % int(data)
+ else:
+ return "s:%i:\"%s\";" % (len(data), data);
+
+ # None / NULL => empty string
+ elif type(data) is types.NoneType:
+ return "s:0:\"\";"
+
+ # I dont know how to serialize this
+ else:
+ raise Exception("Unknown / Unhandled key type (%s)!" % type(data))
+
+
+ def serialize_value(self, data):
+ """
+ Serialize a value.
+ """
+
+ # Integer => integer
+ if type(data) is types.IntType:
+ return "i:%s;" % data
+
+ # Float, Long => double
+ elif type(data) is types.FloatType or type(data) is types.LongType:
+ return "d:%s;" % data
+
+ # String => string or String => int (if string looks like int)
+ # Thanks to Todd DeLuca for noticing that PHP strings that
+ # look like integers are serialized as ints by PHP
+ elif type(data) is types.StringType:
+ if self.is_int(data):
+ return "i:%s;" % int(data)
+ else:
+ return "s:%i:\"%s\";" % (len(data), data);
+
+ # None / NULL
+ elif type(data) is types.NoneType:
+ return "N;";
+
+ # Tuple and List => array
+ # The 'a' array type is the only kind of list supported by PHP.
+ # array keys are automagically numbered up from 0
+ elif type(data) is types.ListType or type(data) is types.TupleType:
+ i = 0
+ out = []
+ # All arrays must have keys
+ for k in data:
+ out.append(self.serialize_key(i))
+ out.append(self.serialize_value(k))
+ i += 1
+ return "a:%i:{%s}" % (len(data), "".join(out))
+
+ # Dict => array
+ # Dict is the Python analogy of a PHP array
+ elif type(data) is types.DictType:
+ out = []
+ for k in data:
+ out.append(self.serialize_key(k))
+ out.append(self.serialize_value(data[k]))
+ return "a:%i:{%s}" % (len(data), "".join(out))
+
+ # Boolean => bool
+ elif type(data) is types.BooleanType:
+ return "b:%i;" % (data == 1)
+
+ # I dont know how to serialize this
+ else:
+ raise Exception("Unknown / Unhandled data type (%s)!" % type(data))
diff --git a/mediawiki/PHPUnserialize.py b/mediawiki/PHPUnserialize.py
new file mode 100644
index 00000000..b59c869c
--- /dev/null
+++ b/mediawiki/PHPUnserialize.py
@@ -0,0 +1,187 @@
+import types, string, re
+
+"""
+Unserialize class for the PHP serialization format.
+
+@version v0.4 BETA
+@author Scott Hurring; scott at hurring dot com
+@copyright Copyright (c) 2005 Scott Hurring
+@license http://opensource.org/licenses/gpl-license.php GNU Public License
+$Id: PHPUnserialize.py,v 1.1 2006/01/08 21:53:19 shurring Exp $
+
+Most recent version can be found at:
+http://hurring.com/code/python/phpserialize/
+
+Usage:
+# Create an instance of the unserialize engine
+u = PHPUnserialize()
+# unserialize some string into python data
+data = u.unserialize(serialized_string)
+
+Please see README.txt for more information.
+"""
+
+class PHPUnserialize(object):
+ """
+ Class to unserialize something from the PHP Serialize format.
+
+ Usage:
+ u = PHPUnserialize()
+ data = u.unserialize(serialized_string)
+ """
+
+ def __init__(self):
+ pass
+
+ def session_decode(self, data):
+ """Thanks to Ken Restivo for suggesting the addition
+ of session_encode
+ """
+ session = {}
+ while len(data) > 0:
+ m = re.match('^(\w+)\|', data)
+ if m:
+ key = m.group(1)
+ offset = len(key)+1
+ (dtype, dataoffset, value) = self._unserialize(data, offset)
+ offset = offset + dataoffset
+ data = data[offset:]
+ session[key] = value
+ else:
+ # No more stuff to decode
+ return session
+
+ return session
+
+ def unserialize(self, data):
+ return self._unserialize(data, 0)[2]
+
+ def _unserialize(self, data, offset=0):
+ """
+ Find the next token and unserialize it.
+ Recurse on array.
+
+ offset = raw offset from start of data
+
+ return (type, offset, value)
+ """
+
+ buf = []
+ dtype = string.lower(data[offset:offset+1])
+
+ #print "# dtype =", dtype
+
+ # 't:' = 2 chars
+ dataoffset = offset + 2
+ typeconvert = lambda x : x
+ chars = datalength = 0
+
+ # int => Integer
+ if dtype == 'i':
+ typeconvert = lambda x : int(x)
+ (chars, readdata) = self.read_until(data, dataoffset, ';')
+ # +1 for end semicolon
+ dataoffset += chars + 1
+
+ # bool => Boolean
+ elif dtype == 'b':
+ typeconvert = lambda x : (int(x) == 1)
+ (chars, readdata) = self.read_until(data, dataoffset, ';')
+ # +1 for end semicolon
+ dataoffset += chars + 1
+
+ # double => Floating Point
+ elif dtype == 'd':
+ typeconvert = lambda x : float(x)
+ (chars, readdata) = self.read_until(data, dataoffset, ';')
+ # +1 for end semicolon
+ dataoffset += chars + 1
+
+ # n => None
+ elif dtype == 'n':
+ readdata = None
+
+ # s => String
+ elif dtype == 's':
+ (chars, stringlength) = self.read_until(data, dataoffset, ':')
+ # +2 for colons around length field
+ dataoffset += chars + 2
+
+ # +1 for start quote
+ (chars, readdata) = self.read_chars(data, dataoffset+1, int(stringlength))
+ # +2 for endquote semicolon
+ dataoffset += chars + 2
+
+ if chars != int(stringlength) != int(readdata):
+ raise Exception("String length mismatch")
+
+ # array => Dict
+ # If you originally serialized a Tuple or List, it will
+ # be unserialized as a Dict. PHP doesn't have tuples or lists,
+ # only arrays - so everything has to get converted into an array
+ # when serializing and the original type of the array is lost
+ elif dtype == 'a':
+ readdata = {}
+
+ # How many keys does this list have?
+ (chars, keys) = self.read_until(data, dataoffset, ':')
+ # +2 for colons around length field
+ dataoffset += chars + 2
+
+ # Loop through and fetch this number of key/value pairs
+ for i in range(0, int(keys)):
+ # Read the key
+ (ktype, kchars, key) = self._unserialize(data, dataoffset)
+ dataoffset += kchars
+ #print "Key(%i) = (%s, %i, %s) %i" % (i, ktype, kchars, key, dataoffset)
+
+ # Read value of the key
+ (vtype, vchars, value) = self._unserialize(data, dataoffset)
+ dataoffset += vchars
+ #print "Value(%i) = (%s, %i, %s) %i" % (i, vtype, vchars, value, dataoffset)
+
+ # Set the list element
+ readdata[key] = value
+
+ # +1 for end semicolon
+ dataoffset += 1
+ #chars = int(dataoffset) - start
+
+ # I don't know how to unserialize this
+ else:
+ raise Exception("Unknown / Unhandled data type (%s)!" % dtype)
+
+
+ return (dtype, dataoffset-offset, typeconvert(readdata))
+
+ def read_until(self, data, offset, stopchar):
+ """
+ Read from data[offset] until you encounter some char 'stopchar'.
+ """
+ buf = []
+ char = data[offset:offset+1]
+ i = 2
+ while char != stopchar:
+ # Consumed all the characters and havent found ';'
+ if i+offset > len(data):
+ raise Exception("Invalid")
+ buf.append(char)
+ char = data[offset+(i-1):offset+i]
+ i += 1
+
+ # (chars_read, data)
+ return (len(buf), "".join(buf))
+
+ def read_chars(self, data, offset, length):
+ """
+ Read 'length' number of chars from data[offset].
+ """
+ buf = []
+ # Account for the starting quote char
+ #offset += 1
+ for i in range(0, length):
+ char = data[offset+(i-1):offset+i]
+ buf.append(char)
+
+ # (chars_read, data)
+ return (len(buf), "".join(buf))
diff --git a/mediawiki/README b/mediawiki/README
new file mode 100644
index 00000000..b664a8a7
--- /dev/null
+++ b/mediawiki/README
@@ -0,0 +1,106 @@
+This is a rough example of integrated mediawiki authentication
+originally written to work on a customized MW (some tables are different from standard)
+so to adapt this to your case you'll most likely need to tweak this
+
+Also keep in mind that probably a better solution would be to create a single signon site.
+
+Author: evgeny.fadeev@gmail.com (Evgeny)
+
+==Minimal directions==
+
+1) Add the following to your settings_local.py (with relevant modifications):
+
+USE_EXTERNAL_LEGACY_LOGIN = True #enable external legacy login
+EXTERNAL_LEGACY_LOGIN_AUTHENTICATION_BACKEND = 'mediawiki.auth.IncludeVirtualAuthenticationBackend'
+EXTERNAL_LEGACY_LOGIN_AUTHENTICATION_MIDDLEWARE = 'mediawiki.middleware.IncludeVirtualAuthenticationMiddleware'
+EXTERNAL_LEGACY_LOGIN_MODULE = 'mediawiki' #current module
+EXTERNAL_LEGACY_LOGIN_HOST = 'yoursite.org' #wiki domain
+EXTERNAL_LEGACY_LOGIN_PORT = 80 #port, probably 80
+EXTERNAL_LEGACY_LOGIN_PROVIDER_NAME = 'My Wiki' #html allowed
+MEDIAWIKI_URL="http://yoursite.org/wiki/index.php"
+MEDIAWIKI_SALT_PASSWORD=True #or False - depending on your LocalSettings.php
+MEDIAWIKI_INDEX_PHP_URL='/wiki/index.php'
+MEDIAWIKI_COOKIE_DOMAIN='.yoursite.org' #for cross subdomain login
+MEDIAWIKI_SESSION_COOKIE_NAME = '' # probably '<dbname>_<dbtblprefix>_session'
+MEDIAWIKI_PHP_SESSION_PREFIX='sess_' #depends on your setup
+MEDIAWIKI_PHP_SESSION_PATH='/var/lib/php/session'
+SESSION_COOKIE_DOMAIN = '.yoursite.org' #use this notation for cross-subdomain login
+
+2) Configure apache to access forum "backend" via the wiki domain.
+Example configuration is in the end of the doc.
+
+3) Install two wiki extensions WsgiInjectableSpecialPage, UserRegister -
+the usual MW way. You might want to disable traditional wiki registration.
+
+4) grep files for 'yourwiki' and change that to your own taste - there are some
+hardcoded urls
+
+5) Templates are in templates/mediawiki - you probably will want to customize them,
+form js media: templates/content/js/mediawiki-login.js
+form css media: templates/content/style/mediawiki-login.css
+
+==Requirements==
+wiki and forum must live in the same mysql database for registration to work,
+however login will work even if this is not the case
+
+you must own both wiki and forum or there must be good trust relationship
+between the owners - because password is shared
+
+==Notes on how external login currently works.==
+password and login are entered in the login form.
+these are checked against mw api
+
+password is saved in the auth_user table (the django way)
+so if you at some point set USE_EXTERNAL_LEGACY_LOGIN = False
+wiki passwords and logins will still work on the forum
+
+login action is partially synchronized btw wiki and forum (from forum to wiki,
+but not the opposite way yet)
+
+when users first register - either on wiki or forum they are logged in on both
+
+on registration they receive a greeting email - you will want to customize messages
+
+technically, on the wiki registration form is injected via apache SSI
+- using include virtual call
+
+there is a possibility for cross-site scripting attack if wiki session is stolen
+
+==Apache setup example for the wiki==
+This assumes that wiki and forum facing the user are on different subdomains.
+Also this setup is just an example - you may do better :).
+Forum setup in apache is described in main osqa INSTALL document - that's extra.
+
+<VirtualHost your.ip:port>
+ ServerAdmin admin@yourwiki.org
+ DocumentRoot /path/to/wiki/root #dir containing wiki directory
+ ServerName yourwiki.org
+ AddOutputFilter INCLUDES .php
+ Alias /backend/content/ /path/to/forum/templates/content/
+ AliasMatch (content\/style\/[^/]*\.css) /path/to/forum/templates/$1
+ AliasMatch (content\/.*) /path/to/forum/templates/$1
+ <Directory /path/to/forum/templates/content>
+ Order deny,allow
+ Allow from all
+ </Directory>
+ WSGIDaemonProcess my-forum-wiki-side #use daemon mode so to avoid potential timezone messups
+ WSGIProcessGroup my-forum-wiki-side
+ WSGIScriptAlias /backend /path/to/forum/cnprog.wsgi
+ CustomLog /var/log/httpd/yourwiki/access_log common
+ ErrorLog /var/log/httpd/yourwiki/error_log
+ LogLevel debug
+ DirectoryIndex index.php index.html
+</VirtualHost>
+<Directory /path/to/wiki/root>
+ Options Includes
+ <IfModule sapi_apache2.c>
+ php_admin_flag engine on
+ php_admin_flag safe_mode off
+ </IfModule>
+ <IfModule mod_php5.c>
+ php_admin_flag engine on
+ php_admin_flag safe_mode off
+ php_admin_value open_basedir "/path/to/wiki/root:.:/tmp" #tmp used for sessions
+ </IfModule>
+</Directory>
+
diff --git a/mediawiki/UserRegister.alias.php b/mediawiki/UserRegister.alias.php
new file mode 100644
index 00000000..0d5c1523
--- /dev/null
+++ b/mediawiki/UserRegister.alias.php
@@ -0,0 +1,7 @@
+<?php
+$messages = array();
+
+/* *** English *** */
+$messages['en'] = array(
+ 'User Register' => array('User Register'),
+);
diff --git a/mediawiki/UserRegister.body.php b/mediawiki/UserRegister.body.php
new file mode 100644
index 00000000..f8c953a9
--- /dev/null
+++ b/mediawiki/UserRegister.body.php
@@ -0,0 +1,14 @@
+<?php
+class UserRegister extends WsgiInjectableSpecialPage {
+ function __construct() {
+ parent::__construct( 'UserRegister',
+ '/backend/account/nmr-wiki/signup/',
+ array(0=>'/backend/content/style/mediawiki-login.css'),
+ array(
+ 0=>'/backend/content/js/jquery-1.2.6.min.js',
+ 1=>'/backend/content/js/mediawiki-login.js'
+ )
+ );
+ }
+}
+
diff --git a/mediawiki/UserRegister.i18n.php b/mediawiki/UserRegister.i18n.php
new file mode 100644
index 00000000..2f8d41d0
--- /dev/null
+++ b/mediawiki/UserRegister.i18n.php
@@ -0,0 +1,6 @@
+<?php
+$messages = array();
+
+$messages['en'] = array(
+ 'userregister' => 'Join the Wiki',
+);
diff --git a/mediawiki/UserRegister.php b/mediawiki/UserRegister.php
new file mode 100644
index 00000000..cff0e69d
--- /dev/null
+++ b/mediawiki/UserRegister.php
@@ -0,0 +1,24 @@
+<?php
+# Alert the user that this is not a valid entry point to MediaWiki if they try to access the special pages file directly.
+if (!defined('MEDIAWIKI')) {
+ echo <<<EOT
+Not a valid entry point.
+EOT;
+ exit( 1 );
+}
+
+$wgExtensionCredits['specialpage'][] = array(
+ 'name' => 'User Registration',
+ 'author' => 'Evgeny Fadeev',
+ 'url' => 'none',
+ 'description' => 'Creates new user account for the Wiki and Q&A forum',
+ 'descriptionmsg' => 'people-page-desc',
+ 'version' => '0.0.0',
+);
+
+$dir = dirname(__FILE__) . '/';
+
+$wgAutoloadClasses['UserRegister'] = $dir . 'UserRegister.body.php'; # Tell MediaWiki to load the extension body.
+$wgExtensionMessagesFiles['UserRegister'] = $dir . 'UserRegister.i18n.php';
+$wgExtensionAliasesFiles['UserRegister'] = $dir . 'UserRegister.alias.php';
+$wgSpecialPages['UserRegister'] = 'UserRegister'; # Let MediaWiki know about your new special page.
diff --git a/mediawiki/WsgiInjectableSpecialPage.php b/mediawiki/WsgiInjectableSpecialPage.php
new file mode 100644
index 00000000..d23f22e8
--- /dev/null
+++ b/mediawiki/WsgiInjectableSpecialPage.php
@@ -0,0 +1,80 @@
+<?php
+
+class WsgiInjectableSpecialPage extends SpecialPage {
+ var $default_wsgi_command;
+ function __construct($page_name,$default_wsgi_command,$css='',$scripts='',$wsgi_prefix=''){
+ parent::__construct($page_name);
+ wfLoadExtensionMessages($page_name);
+ $this->default_wsgi_command = $default_wsgi_command;
+ $this->css = $css;
+ $this->scripts = $scripts;
+ $this->wsgi_prefix = $wsgi_prefix;
+ }
+ function execute($par){
+ global $wgWsgiScriptPath, $wgRequest, $wgOut, $wgHeader;
+ $wgWsgiScriptPath = '';
+ if ($this->wsgi_prefix != ''){
+ $wsgi_call = $this->wsgi_prefix;
+ }
+ else {
+ $wsgi_call = $wgWsgiScriptPath;
+ }
+
+ $this->setHeaders();
+
+ if ($this->css != ''){
+ if (is_array($this->css)){
+ foreach($this->css as $css){
+ $wgHeader->addCSS($css);
+ }
+ }
+ else{
+ $wgHeader->addCSS($this->css);
+ }
+ }
+ if ($this->scripts != ''){
+ if (is_array($this->scripts)){
+ foreach($this->scripts as $script){
+ $wgHeader->addScript($script);
+ }
+ }
+ else{
+ $wgHeader->addScript($this->css);
+ }
+ }
+
+ #add command
+ if (!is_null($wgRequest->getVal('command'))){
+ $wsgi_call .= $wgRequest->getVal('command');
+ }
+ else {
+ #why is this not working?
+ $wsgi_call .= $this->default_wsgi_command;
+ }
+ #add session key
+ $session_name = ini_get('session.name');#session_name();
+ $session = '';
+ if (array_key_exists($session_name, $_COOKIE)){
+ $session = $_COOKIE[$session_name];
+ }
+ $wsgi_call .= "?session=${session}";
+
+ #add posted request variables
+ if ($wgRequest->wasPosted()){
+ $data = $wgRequest->data;
+ foreach ($data as $key => $value){
+ if ($key != 'title'){
+ $wsgi_call .= "&${key}=${value}";
+ }
+ }
+ $wsgi_call .= '&was_posted=true';
+ }
+ else {
+ $wsgi_call .= '&was_posted=false';
+ }
+
+ #print the include statement called as GET request
+ $wgOut->addHTML("<!--#include virtual=\"${wsgi_call}\"-->");
+ #$wgOut->addHTML("<!-- ${wsgi_call} -->"); #print this only for debugging
+ }
+}
diff --git a/mediawiki/__init__.py b/mediawiki/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/mediawiki/__init__.py
diff --git a/mediawiki/api.py b/mediawiki/api.py
new file mode 100644
index 00000000..912de041
--- /dev/null
+++ b/mediawiki/api.py
@@ -0,0 +1,138 @@
+#this file contains stub functions that can be extended to support
+#connect legacy login with external site
+from django.conf import settings
+from django_authopenid.models import ExternalLoginData
+import httplib
+import urllib
+import Cookie
+import cookielib
+from django import forms
+import xml.dom.minidom as xml
+import logging
+from models import User as MWUser
+
+def login(request,user):
+ """performs the additional external login operation
+ """
+ pass
+
+def set_login_cookies(response,user):
+ #should be unique value by design
+ try:
+ eld = ExternalLoginData.objects.get(user=user)
+
+ data = eld.external_session_data
+ dom = xml.parseString(data)
+ login_response = dom.getElementsByTagName('login')[0]
+ userid = login_response.getAttribute('lguserid')
+ username = login_response.getAttribute('lgusername')
+ token = login_response.getAttribute('lgtoken')
+ prefix = login_response.getAttribute('cookieprefix').decode('utf-8')
+ sessionid = login_response.getAttribute('sessionid')
+
+ c = {}
+ c[prefix + 'UserName'] = username
+ c[prefix + 'UserID'] = userid
+ c[prefix + 'Token'] = token
+ c[prefix + '_session'] = sessionid
+
+ logging.debug('have cookies ' + str(c))
+
+ #custom code that copies cookies from external site
+ #not sure how to set paths and domain of cookies here
+ domain = settings.MEDIAWIKI_COOKIE_DOMAIN
+ for key in c:
+ if c[key]:
+ response.set_cookie(str(key),\
+ value=str(c[key]),\
+ domain=domain)
+ for c in response.cookies.values():
+ logging.debug(c.output())
+ except ExternalLoginData.DoesNotExist:
+ #this must be an OpenID login
+ pass
+
+#function to perform external logout, if needed
+def logout(request):
+ pass
+
+#should raise User.DoesNotExist or pass
+def clean_username(username):
+ return username
+
+def check_password(username,password):
+ """connects to external site and submits username/password pair
+ return True or False depending on correctness of login
+ saves remote unique id and remote session data in table ExternalLoginData
+ may raise forms.ValidationError
+ """
+ host = settings.EXTERNAL_LEGACY_LOGIN_HOST
+ port = settings.EXTERNAL_LEGACY_LOGIN_PORT
+ ext_site = httplib.HTTPConnection(host,port)
+
+ print 'connected to %s:%s' % (str(host),str(port))
+
+ #custom code. this one does authentication through
+ #MediaWiki API
+ params = urllib.urlencode({'action':'login','format':'xml',
+ 'lgname':username,'lgpassword':password})
+ headers = {"Content-type": "application/x-www-form-urlencoded",
+ 'User-Agent':"User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.7) Gecko/2009021910 Firefox/3.0.7",
+ "Accept": "text/xml"}
+ ext_site.request("POST","/wiki/api.php",params,headers)
+ response = ext_site.getresponse()
+ if response.status != 200:
+ raise forms.ValidationError('error ' + response.status + ' ' + response.reason)
+ data = response.read().strip()
+ ext_site.close()
+
+ print data
+
+ dom = xml.parseString(data)
+ login = dom.getElementsByTagName('login')[0]
+ result = login.getAttribute('result')
+
+ if result == 'Success':
+ username = login.getAttribute('lgusername')
+ try:
+ eld = ExternalLoginData.objects.get(external_username=username)
+ except ExternalLoginData.DoesNotExist:
+ eld = ExternalLoginData()
+ eld.external_username = username
+ eld.external_session_data = data
+ eld.save()
+ return True
+ else:
+ error = login.getAttribute('details')
+ raise forms.ValidationError(error)
+ return False
+
+def createuser(username,email,password):
+ pass
+
+#retrieve email address
+def get_email(username,password):
+ try:
+ u = MWUser.objects.get(user_name=username)
+ return u.user_email
+ except MWUser.DoesNotExist:
+ return ''
+
+#try to get full name from mediawiki
+def get_screen_name(username,password):
+ try:
+ u = MWUser.objects.get(user_name=username)
+ full_name = u' '.join((u.user_first_name, u.user_last_name)).strip()
+ if full_name != u'':
+ return full_name
+ else:
+ return username
+ except MWUser.DoesNotExist:
+ return username
+
+def connect_local_user_to_external_user(user, login, password):
+ try:
+ u = MWUser.objects.get(user_name=login)
+ user.mediawiki_user = u
+ except MWUser.DoesNotExist:
+ pass
diff --git a/mediawiki/auth.py b/mediawiki/auth.py
new file mode 100644
index 00000000..ee367dc1
--- /dev/null
+++ b/mediawiki/auth.py
@@ -0,0 +1,59 @@
+from mediawiki.models import User as MWUser
+from django.contrib.auth.models import User
+from django_authopenid.models import ExternalLoginData
+from django.conf import settings
+import logging
+from PHPUnserialize import PHPUnserialize
+import os
+
+class php(object):
+ @staticmethod
+ def get_session_data(session):
+ prefix = settings.MEDIAWIKI_PHP_SESSION_PREFIX
+ path = settings.MEDIAWIKI_PHP_SESSION_PATH
+ file = os.path.join(path,prefix + session)
+ #file may not exist
+ data = open(file).read()
+ u = PHPUnserialize()
+ return u.session_decode(data)
+
+class IncludeVirtualAuthenticationBackend(object):
+ def authenticate(self,token=None):
+ logging.debug('authenticating session %s' % token)
+ try:
+ php_session = php.get_session_data(token)
+ #todo: report technical errors to root
+ except:
+ #Fail condition 1. Session data cannot be retrieved
+ logging.debug('session %s cannot be retrieved' % str(token))
+ return None
+ try:
+ name = php_session['wsUserName']
+ id = php_session['wsUserID']
+ except:
+ #Fail condition 2. Data misses keys
+ logging.debug('missing data in session table')
+ return None
+ try:
+ logging.debug('trying to find user %s id=%s in the MW database' % (name,id))
+ wu = MWUser.objects.get(user_name=name,user_id=id)
+ except MWUser.DoesNotExist:
+ #Fail condition 3. User does not match session data
+ logging.debug('could not find wiki user who owns session')
+ return None
+ try:
+ logging.debug('trying to get external login data for mw user %s' % name)
+ eld = ExternalLoginData.objects.get(external_username=name)
+ #update session data and save?
+ return eld.user #may be none!
+ except ExternalLoginData.DoesNotExist:
+ #Fail condition 4. no external login data - user never logged in through django
+ #using the wiki login and password
+ logging.debug('no association found for MW user %s with django user' % name)
+ return None
+
+ def get_user(self, user_id):
+ try:
+ return User.objects.get(pk=user_id)
+ except User.DoesNotExist:
+ return None
diff --git a/mediawiki/forms.py b/mediawiki/forms.py
new file mode 100644
index 00000000..bac39b5a
--- /dev/null
+++ b/mediawiki/forms.py
@@ -0,0 +1,164 @@
+from utils.forms import NextUrlField, UserNameField, UserEmailField, SetPasswordForm
+from django import forms
+from django.forms import ValidationError
+from models import User as MWUser
+from models import TITLE_CHOICES
+from django.contrib.auth.models import User
+from django.utils.translation import ugettext as _
+from django.utils.safestring import mark_safe
+from django.contrib.formtools.wizard import FormWizard
+from forum.forms import EditUserEmailFeedsForm, SimpleEmailSubscribeForm
+from django.forms import ValidationError
+from recaptcha_django import ReCaptchaField
+from utils.forms import StrippedNonEmptyCharField
+from forum.templatetags import extra_tags as forum_extra_tags
+
+#make better translations in your language django.po
+EMAIL_FEED_CHOICES = (
+ ('y',_('okay, let\'s try!')),
+ ('n',_('no OSQA community email please, thanks'))
+)
+
+wiki_account_taken_msg = _('Wiki site already has this account, if it is yours perhaps you can '
+ 'just try to log in with it?<br/>'
+ 'Otherwise, please pick another login name.')
+
+class RegisterForm(SetPasswordForm, SimpleEmailSubscribeForm):
+ login_name = UserNameField(label=_('Login name'), \
+ db_model=MWUser, \
+ db_field='user_name', \
+ error_messages={ \
+ 'required':_('Please enter login name above, it is required for the Wiki site'), \
+ 'taken': mark_safe(wiki_account_taken_msg) \
+ }
+ )
+ next = NextUrlField()
+ email = UserEmailField()
+ screen_name = UserNameField(label=mark_safe(_('Please type your nickname below')), \
+ skip_clean=True, \
+ required=False)
+ first_name = StrippedNonEmptyCharField(max_length=255,label=mark_safe(_('First name')),
+ error_messages={'required':_('First name is required')}
+ )
+ last_name = StrippedNonEmptyCharField(max_length=255,label=_('Last name'),
+ error_messages={'required':_('Last name is required')}
+ )
+ #cannot be just "title" because there would be a conflict with "title" field used for MW!!!
+ user_title = forms.ChoiceField(choices=TITLE_CHOICES, label=_('Title (optional)'))
+ use_separate_screen_name = forms.BooleanField(
+ label=mark_safe(_('I prefer (or have to) to use a separate forum screen name')),
+ required=False,
+ )
+ #subscribe = forms.ChoiceField(widget=forms.widgets.RadioSelect, \
+ # error_messages={'required':_('please choose one of the options above')},
+ # choices= EMAIL_FEED_CHOICES)
+ recaptcha = ReCaptchaField()
+
+ class Media:
+ css={'all':(forum_extra_tags.href('/content/style/mediawiki-login.css'),),}
+ js=(forum_extra_tags.href('/content/js/mediawiki-login.js'),)
+
+ def add_screen_name_error(self, err):
+ if 'screen_name' in self.cleaned_data:
+ del self.cleaned_data['screen_name']
+ error_list = self._errors.get('screen_name',forms.util.ErrorList([]))
+ if isinstance(err, forms.util.ErrorList):
+ error_list.extend(err)
+ else:
+ error_list.append(err)
+ self._errors['screen_name'] = error_list
+
+ def clean(self):
+ #this method cleans screen_name and use_separate_screen_name
+ screen_name = self.cleaned_data.get('screen_name', '')
+
+ if 'use_separate_screen_name' in self.cleaned_data \
+ and self.cleaned_data['use_separate_screen_name']:
+ if screen_name == '':
+ msg = _('please enter an alternative screen name or uncheck the box above')
+ self.add_screen_name_error(msg)
+ else:
+ try:
+ screen_name = self.fields['screen_name'].clean(screen_name)
+ self.final_clean_screen_name(screen_name)
+ except ValidationError, e:
+ self.add_screen_name_error(e)
+ else:
+ if screen_name != '':
+ self.add_screen_name_error(_('sorry, to use alternative screen name, please confirm it by checking the box above'))
+ else:
+ #build screen name from first and last names
+ first = self.cleaned_data.get('first_name',None)
+ last = self.cleaned_data.get('last_name',None)
+ if first and last:
+ screen_name = u'%s %s' % (first,last)
+ self.final_clean_screen_name(screen_name)
+ return self.cleaned_data
+
+ def final_clean_screen_name(self,name):
+ try:
+ u = User.objects.get(username=name)
+ msg = _('Screen name <strong>%(real_name)s</strong> is somehow already taken on the forum.<br/>'
+ 'Unfortunately you will have to pick a separate screen name, but of course '
+ 'there is no need to change the first name and last name entries.<br/>'
+ 'Please send us your feedback if you feel there might be a mistake. '
+ 'Sorry for the inconvenience.')\
+ % {'real_name':name}
+ self.add_screen_name_error(mark_safe(msg))
+ except:
+ self.cleaned_data['screen_name'] = name
+
+ #overridden validation for UserNameField
+ def clean_login_name(self):
+ try:
+ MWUser.objects.get(user_name=self.cleaned_data['login_name'])
+ del self.cleaned_data['login_name']
+ raise ValidationError(_('sorry this login name is already taken, please try another'))
+ except:
+ return self.cleaned_data['login_name']
+
+class RegisterFormWizard(FormWizard):
+ def done(self, request, form_list):
+ data = form_list[0].cleaned_data
+ login_name = data['login_name']
+ password = data['password']
+ first_name = data['first_name']
+ last_name = data['last_name']
+ screen_name = data['screen_name']
+ email = data['email']
+ subscribe = data['subscribe']
+ next = data['next']
+
+ #register mediawiki user
+ mwu = MWUser(
+ user_name=login_name,
+ user_password=password,
+ user_first_name = first_name,
+ user_last_name = last_name,
+ user_email = email
+ )
+ mwu.save()
+
+ #register local user
+ User.objects.create_user(screen_name, email, password)
+ u = authenticate(username=screen_name, password=password)
+ u.mediawiki_user = mwu
+ u.save()
+
+ #save email feed settings
+ EFF = EditUserEmailFeedsForm
+ if subscribe == 'y':
+ email_settings_form = EFF()
+ else:
+ email_settings_form = EFF(initial=EFF.NO_EMAIL_INITIAL)
+ email_settings_form.save(u)
+
+ #create welcome message
+ u.message_set.create(message=_('Welcome to Q&A forum!'))
+ return HttpResponseRedirect(next)
+
+ def get_template(self, step):
+ if step == 0:
+ return 'mediawiki/mediawiki_signup.html'
+ elif step == 1:
+ return 'notarobot.html'
diff --git a/mediawiki/junk.py b/mediawiki/junk.py
new file mode 100644
index 00000000..e67d492a
--- /dev/null
+++ b/mediawiki/junk.py
@@ -0,0 +1,2 @@
+def junk():
+ pass
diff --git a/mediawiki/middleware.py b/mediawiki/middleware.py
new file mode 100644
index 00000000..a46f486a
--- /dev/null
+++ b/mediawiki/middleware.py
@@ -0,0 +1,57 @@
+from django.contrib import auth
+from django.core.exceptions import ImproperlyConfigured
+from django.conf import settings
+import logging
+import traceback
+import sys
+
+class IncludeVirtualAuthenticationMiddleware(object):
+ def process_request(self,request):
+ """in this type of authentication the mw session token is passed via
+ "session" request parameter and authentication happens on every
+ request
+ """
+ logging.debug('trying include virtual milldeware')
+ if not hasattr(request,'user'):
+ raise ImproperlyConfigured(
+ "The include virtual mediawiki authentication middleware requires the"
+ " authentication middleware to be installed. Edit your"
+ " MIDDLEWARE_CLASSES setting to insert"
+ " 'django.contrib.auth.middleware.AuthenticationMiddleware'"
+ " before the IncludeVirtualAuthenticationMiddleware class."
+ )
+
+ session = None
+ request.is_include_virtual = False
+ if request.is_ajax():
+ logging.debug('have ajax request')
+ cookie_name = settings.MEDIAWIKI_SESSION_COOKIE_NAME
+ if cookie_name in request.COOKIES:
+ session = request.COOKIES[cookie_name]
+ logging.debug('ajax call has session %s' % session)
+ else:
+ logging.debug('dont have cookie')
+ else:
+ if request.REQUEST.has_key('session'):
+ session = request.REQUEST['session']
+ request.is_include_virtual = True
+ logging.debug('I am virtual')
+ if request.REQUEST.get('was_posted','false') == 'true':
+ data = request.GET.copy()
+ data['recaptcha_ip_field'] = request.META['REMOTE_ADDR']
+ request.GET = data
+ logging.debug('REQUEST is now %s' % str(request.GET))
+ user = auth.authenticate(token=session) #authenticate every time
+ if user:
+ request.user = user
+ auth.login(request,user)
+ #else I probably need to forbid access
+ #raise ImproperlyConfigured(
+ # "The include virtual mediawiki authentication middleware requires the"
+ # "'session' request parameter set in the including document"
+ #)
+
+ def process_exception(self,request,exception):
+ exceptionType, exceptionValue, exceptionTraceback = sys.exc_info()
+ logging.debug('\n'.join(traceback.format_tb(exceptionTraceback)))
+ logging.debug('have exception %s %s' % (exceptionType,exceptionValue))
diff --git a/mediawiki/models.py b/mediawiki/models.py
new file mode 100644
index 00000000..e37aec32
--- /dev/null
+++ b/mediawiki/models.py
@@ -0,0 +1,312 @@
+# This is an auto-generated Django model module.
+# You'll have to do the following manually to clean this up:
+# * Rearrange models' order
+# * Make sure each model has one field with primary_key=True
+# Feel free to rename the models, but don't rename db_table values or field names.
+#
+# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [appname]'
+# into your database.
+
+table_prefix = u'nmrwiki'
+from django.db import models
+import re
+from django.conf import settings
+import logging
+from django.contrib.auth.models import User as DjangoUser
+from django.utils.translation import ugettext as _
+import hashlib
+import time
+import random
+
+MW_TS = '%Y%m%d%H%M%S'
+
+TITLE_CHOICES = (
+ ('none',_('----')),
+ ('prof',_('Prof.')),
+ ('dr',_('Dr.')),
+)
+
+class User(models.Model):
+ user_id = models.IntegerField(primary_key=True,db_column='user_id')
+ user_name = models.CharField(max_length=765)
+ user_real_name = models.CharField(max_length=765)
+ user_password = models.TextField()
+ user_newpassword = models.TextField()
+ user_newpass_time = models.CharField(max_length=14, blank=True)
+ user_email = models.TextField()
+ user_options = models.TextField()
+ user_touched = models.CharField(max_length=14)
+ user_token = models.CharField(max_length=32)
+ user_email_authenticated = models.CharField(max_length=14, blank=True)
+ user_email_token = models.CharField(max_length=32, blank=True)
+ user_email_token_expires = models.CharField(max_length=14, blank=True)
+ user_registration = models.CharField(max_length=14, blank=True)
+ user_editcount = models.IntegerField(null=True, blank=True)
+ user_last_name = models.CharField(max_length=765, blank=True)
+ user_first_name = models.CharField(max_length=765, blank=True)
+ user_reason_to_join = models.CharField(max_length=765, blank=True)
+ user_title = models.CharField(max_length=16, blank=True, choices=TITLE_CHOICES)
+ class Meta:
+ db_table = table_prefix + u'user'
+ managed = False
+
+ def set_default_options(self):
+ default_options = {
+ 'quickbar':1,
+ 'underline':2,
+ 'cols':80,
+ 'rows':25,
+ 'searchlimit':20,
+ 'contextlines':5,
+ 'contextchars':50,
+ 'skin':'false',
+ 'math':1,
+ 'rcdays':7,
+ 'rclimit':50,
+ 'wllimit':250,
+ 'highlightbroken':1,
+ 'stubthreshold':0,
+ 'previewontop':1,
+ 'editsection':1,
+ 'editsectiononrightclick':0,
+ 'showtoc':1,
+ 'showtoolbar':1,
+ 'date':'default',
+ 'imagesize':2,
+ 'thumbsize':2,
+ 'rememberpassword':0,
+ 'enotifwatchlistpages':0,
+ 'enotifusertalkpages':1,
+ 'enotifminoredits':0,
+ 'enotifrevealaddr':0,
+ 'shownumberswatching':1,
+ 'fancysig':0,
+ 'externaleditor':0,
+ 'externaldiff':0,
+ 'showjumplinks':1,
+ 'numberheadings':0,
+ 'uselivepreview':0,
+ 'watchlistdays':3.0,
+ 'usenewrc':1,
+ }
+ self.user_options = '\n'.join(
+ map(lambda opt: '%s=%s' % (opt[0], str(opt[1])),
+ default_options.items())
+ )
+
+ def set_password_and_token(self,password):
+ p = hashlib.md5(password).hexdigest()
+ if hasattr(settings,'MEDIAWIKI_SALT_PASSWORD') and settings.MEDIAWIKI_SALT_PASSWORD == True:
+ p = hashlib.md5('%d-%s' % (self.user_id, p)).hexdigest()
+ self.user_password = p
+ self.user_token = hashlib.md5(p + str(time.time())).hexdigest()
+
+ def get_name(self):
+ if self.user_real_name:
+ if re.search(r'\S',self.user_real_name):
+ return self.user_real_name
+ return self.user_name + ' (nickname)'
+
+ def get_html(self):
+ return '<a href="%s">%s</a>' % (self.get_absolute_url(),self.get_name())
+
+ def get_absolute_url(self):
+ url = settings.MEDIAWIKI_URL + '?title=User:' + self.user_name
+ return url
+
+class UserProfile(models.Model):
+ nup_user_id = models.ForeignKey(User,primary_key=True,db_column='nup_user_id')
+ nup_about = models.CharField(max_length=765, blank=True)
+ nup_position_title = models.CharField(max_length=765, blank=True)
+ nup_position_type = models.CharField(max_length=765, blank=True)
+ nup_employer_division = models.CharField(max_length=765, blank=True)
+ nup_employer_company = models.CharField(max_length=765, blank=True)
+ nup_employer_type = models.CharField(max_length=765, blank=True)
+ nup_employment_status = models.CharField(max_length=45, blank=True)
+ nup_profession = models.CharField(max_length=765, blank=True)
+ nup_city = models.CharField(max_length=765, blank=True)
+ nup_state = models.CharField(max_length=765, blank=True)
+ nup_country = models.CharField(max_length=765, blank=True)
+ nup_lattitude = models.FloatField(null=True, blank=True)
+ nup_longitude = models.FloatField(null=True, blank=True)
+ nup_hiring = models.IntegerField(null=True, blank=True)
+ nup_hunting = models.IntegerField(null=True, blank=True)
+ nup_education = models.TextField(blank=True)
+ nup_websites = models.TextField(blank=True)
+ nup_interests = models.TextField(blank=True)
+ nup_job_ad = models.TextField(blank=True)
+ nup_job_ad_title = models.CharField(max_length=765, blank=True)
+ nup_job_ad_active = models.IntegerField(null=True, blank=True)
+ nup_expertise = models.TextField(blank=True)
+ nup_is_approved = models.BooleanField()
+ class Meta:
+ db_table = table_prefix + u'new_user_profile'
+ managed = False
+
+class RecentChanges(models.Model):
+ rc_id = models.AutoField(primary_key=True, db_column='rc_id')
+ rc_timestamp = models.CharField(max_length=14)
+ rc_cur_time = models.CharField(max_length=14)
+ rc_user = models.ForeignKey(User, db_column='rc_user')
+ rc_user_text = models.CharField(max_length=765)
+ rc_namespace = models.IntegerField()
+ rc_title = models.CharField(max_length=765)
+ rc_comment = models.CharField(max_length=765)
+ rc_minor = models.IntegerField()
+ rc_bot = models.IntegerField()
+ rc_new = models.IntegerField()
+ rc_cur_id = models.IntegerField()
+ rc_this_oldid = models.IntegerField()
+ rc_last_oldid = models.IntegerField()
+ rc_type = models.IntegerField()
+ rc_moved_to_ns = models.IntegerField()
+ rc_moved_to_title = models.CharField(max_length=765)
+ rc_patrolled = models.IntegerField()
+ rc_ip = models.CharField(max_length=40)
+ rc_old_len = models.IntegerField(null=True, blank=True)
+ rc_new_len = models.IntegerField(null=True, blank=True)
+ rc_deleted = models.IntegerField()
+ rc_logid = models.ForeignKey('Logging', db_column='rc_logid')
+ rc_log_type = models.CharField(max_length=255, blank=True)
+ rc_log_action = models.CharField(max_length=255, blank=True)
+ rc_params = models.TextField(blank=True)
+ class Meta:
+ db_table = table_prefix + u'recentchanges'
+ managed = False
+
+class Logging(models.Model):
+ log_id = models.AutoField(primary_key=True)
+ log_type = models.CharField(max_length=10)
+ log_action = models.CharField(max_length=10)
+ log_timestamp = models.CharField(max_length=14)
+ log_user = models.ForeignKey(User,db_column='log_user')
+ log_namespace = models.IntegerField()
+ log_title = models.CharField(max_length=765)
+ log_comment = models.CharField(max_length=765)
+ log_params = models.TextField()
+ log_deleted = models.IntegerField()
+ class Meta:
+ db_table = table_prefix + u'logging'
+ managed = False
+
+ def show_in_recent_changes(self, ip=None, rc_minor=False):
+ #to call this method self object must already exist in DB
+ if self.log_type == 'newusers' and self.log_action=='create':
+ rc = RecentChanges(
+ rc_ip=ip,
+ rc_minor=int(rc_minor),
+ rc_deleted=0,
+ rc_bot=0,
+ rc_new=0,
+ rc_moved_to_title='',
+ rc_moved_to_ns=0,
+ rc_this_oldid=0,
+ rc_last_oldid=0,
+ rc_patrolled=1,
+ rc_old_len=None,
+ rc_new_len=None,
+ rc_logid=self,
+ rc_user=self.log_user,
+ rc_user_text=self.log_user.user_name,
+ rc_log_type=self.log_type,
+ rc_log_action=self.log_action,
+ rc_timestamp = self.log_timestamp,
+ rc_cur_time = self.log_timestamp,
+ rc_title='Log/newusers',
+ rc_namespace=-1, #-1 special, 2 is User namespace
+ rc_params=self.log_params,
+ rc_comment=_('Welcome new user!'),
+ rc_type=3,#MW RCLOG constant from Defines.php
+ rc_cur_id=0,
+ )
+ rc.save()
+ else:
+ raise NotImplementedError()
+
+
+class Page(models.Model):
+ page_id = models.AutoField(primary_key=True)
+ page_namespace = models.IntegerField(unique=True)
+ page_title = models.CharField(max_length=765)
+ page_restrictions = models.TextField()
+ page_counter = models.IntegerField()
+ page_is_redirect = models.IntegerField()
+ page_is_new = models.IntegerField()
+ page_random = models.FloatField()
+ page_touched = models.CharField(max_length=14)
+ page_latest = models.IntegerField()
+ page_len = models.IntegerField()
+ class Meta:
+ db_table = table_prefix + u'page'
+ managed = False
+ def save(self):
+ raise Exception('WikiUser table is read-only in this application')
+
+class PageLinks(models.Model):
+ pl_from = models.ForeignKey(Page)
+ pl_namespace = models.IntegerField()
+ pl_title = models.CharField(max_length=765)
+ class Meta:
+ db_table = table_prefix + u'pagelinks'
+ managed = False
+ def save(self):
+ raise Exception('WikiUser table is read-only in this application')
+
+class Revision(models.Model):
+ rev_id = models.IntegerField(unique=True)
+ rev_page = models.IntegerField()
+ rev_text_id = models.IntegerField()
+ rev_comment = models.TextField()
+ rev_user = models.IntegerField()
+ rev_user_text = models.CharField(max_length=765)
+ rev_timestamp = models.CharField(max_length=14)
+ rev_minor_edit = models.IntegerField()
+ rev_deleted = models.IntegerField()
+ rev_len = models.IntegerField(null=True, blank=True)
+ rev_parent_id = models.IntegerField(null=True, blank=True)
+ class Meta:
+ db_table = table_prefix + u'revision'
+ managed = False
+
+class Text(models.Model):
+ old_id = models.IntegerField(primary_key=True)
+ old_text = models.TextField()
+ old_flags = models.TextField()
+ class Meta:
+ db_table = table_prefix + u'text'
+ managed = False
+
+#nmrwiki_stats table may be of interest
+
+class UserGroups(models.Model):
+ ug_user = models.ForeignKey(User,primary_key=True)
+ ug_group = models.CharField(max_length=16)
+ class Meta:
+ db_table = table_prefix + u'user_groups'
+ managed = False
+
+def user_get_absolute_url(user):
+ return user.mediawiki_user.get_absolute_url()
+
+def user_get_html(user):
+ return user.mediawiki_user.get_html()
+
+def user_has_valid_email(user):
+ if user.mediawiki_user.user_email_authenticated:
+ return True
+ else:
+ return False
+
+def user_get_description_for_admin(user):
+ out = user.get_html() + ' (%s)' % user.username
+ if user.has_valid_email():
+ out += ' - has valid email'
+ else:
+ out += ' - <em>no email!</em>'
+ return out
+
+DjangoUser.add_to_class('mediawiki_user',models.ForeignKey(User, null=True))
+DjangoUser.add_to_class('get_wiki_profile_url',user_get_absolute_url)
+DjangoUser.add_to_class('get_wiki_profile_url_html',user_get_html)
+DjangoUser.add_to_class('get_description_for_admin',user_get_description_for_admin)
+DjangoUser.add_to_class('has_valid_wiki_email',user_has_valid_email)
diff --git a/mediawiki/templatetags/__init__.py b/mediawiki/templatetags/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/mediawiki/templatetags/__init__.py
diff --git a/mediawiki/templatetags/mediawikitags.py b/mediawiki/templatetags/mediawikitags.py
new file mode 100644
index 00000000..b21789f4
--- /dev/null
+++ b/mediawiki/templatetags/mediawikitags.py
@@ -0,0 +1,62 @@
+from django import template
+from django.template.defaultfilters import stringfilter
+from django.conf import settings
+import logging
+
+register = template.Library()
+
+#template tags
+class MWPluginFormActionNode(template.Node):
+ def __init__(self, wiki_page, form_action):
+ self.form_action = ''.join(form_action[1:-1])
+ self.wiki_page = ''.join(wiki_page[1:-1])
+ def render(self, context):
+ out = ('<input type="hidden" name="title" value="%s"/>' \
+ + '<input type="hidden" name="command" value="%s"/>') \
+ % (self.wiki_page, self.form_action)
+ return out
+
+def curry_up_to_two_argument_tag(TagNodeClass):
+ def do_the_action_func(parser,token):
+ args = token.split_contents()
+ if len(args) > 3:
+ tagname = token.contents.split()[0]
+ raise template.TemplateSyntaxError, \
+ '%s tag requires two arguments or less' % tagname
+ if len(args) > 1:
+ argument1 = ''.join(args[1][1:-1])
+ else:
+ argument1 = None
+ if len(args) == 3:
+ argument2 = ''.join(args[2][1:-1])
+ else:
+ argument2 = None
+ return TagNodeClass(argument1, argument2)
+ return do_the_action_func
+
+def do_mw_plugin_form_action(parser,token):
+ args = token.split_contents()
+ if len(args) != 3:
+ tagname = token.contents.split()[0]
+ raise template.TemplateSyntaxError, \
+ '%s tag requires two arguments' % tagname
+ return MWPluginFormActionNode(args[1],args[2])
+
+class MediaWikiPluginUrlNode(template.Node):
+ """will return either wiki url, a particular page url
+ or a page with command argument to be interpreted by the plugin
+ """
+ def __init__(self,wiki_page=None,url=None):
+ self.url = url
+ self.wiki_page = wiki_page
+ def render(self,context):
+ title_token = '?title=%s' % self.wiki_page
+ cmd_token = '&command=%s' % self.url
+ if self.wiki_page == None:
+ return settings.MEDIAWIKI_URL
+ if self.url == None:
+ return settings.MEDIAWIKI_URL + title_token
+ return settings.MEDIAWIKI_URL + title_token + cmd_token
+
+register.tag('mw_plugin_form_action',do_mw_plugin_form_action)
+register.tag('mw_plugin_url',curry_up_to_two_argument_tag(MediaWikiPluginUrlNode))
diff --git a/mediawiki/views.py b/mediawiki/views.py
new file mode 100644
index 00000000..012d6f42
--- /dev/null
+++ b/mediawiki/views.py
@@ -0,0 +1,192 @@
+#this file contains stub functions that can be extended to support
+#connect legacy login with external site
+#from django import forms
+import time
+from models import User as MWUser
+from models import Logging
+from models import MW_TS
+import api
+from django.shortcuts import render_to_response
+from django.utils.translation import ugettext as _
+from django.template import RequestContext
+from django.http import HttpResponseRedirect
+from forms import RegisterForm
+from forum.forms import SimpleEmailSubscribeForm
+from forum.models import Question
+from django.contrib.auth.models import User
+from django.contrib.auth import authenticate, login
+from django.http import HttpResponseRedirect
+from django.db import transaction
+from django_authopenid.models import ExternalLoginData
+from django_authopenid.views import not_authenticated
+from django.template import loader
+from django.core.mail import send_mail
+from django.conf import settings
+from django.utils.safestring import mark_safe
+import hashlib
+import random
+
+#not a view, but uses request and templates
+def send_welcome_email(request, wiki_user, django_user):
+ random.seed()
+ confirmation_token = '%032x' % random.getrandbits(128)
+ wiki_user.user_email_token = hashlib.md5(confirmation_token).hexdigest()
+ wiki_user.user_email_token_expires = time.strftime(MW_TS,(time.gmtime(time.time() + 7*24*60*60)))
+ wiki_user.save()
+
+ link = 'http://' + settings.EXTERNAL_LEGACY_LOGIN_HOST \
+ + settings.MEDIAWIKI_INDEX_PHP_URL \
+ + '?title=Special:Confirmemail/' \
+ + confirmation_token
+
+ pw_link = 'http://' + settings.EXTERNAL_LEGACY_LOGIN_HOST \
+ + settings.MEDIAWIKI_INDEX_PHP_URL \
+ + '?title=Password_recovery'
+
+ if wiki_user.user_title == 'prof':
+ template_name = 'mediawiki/welcome_professor_email.txt'
+ else:
+ template_name = 'mediawiki/welcome_email.txt'
+ t = loader.get_template(template_name)
+
+ data = {
+ 'email_confirmation_url':mark_safe(link),
+ 'admin_email':settings.DEFAULT_FROM_EMAIL,
+ 'first_name':wiki_user.user_first_name,
+ 'last_name':wiki_user.user_last_name,
+ 'login_name':wiki_user.user_name,
+ 'title':wiki_user.user_title,
+ 'user_email':wiki_user.user_email,
+ 'forum_screen_name':django_user.username,
+ 'password_recovery_url':mark_safe(pw_link),
+ }
+ body = t.render(RequestContext(request,data))
+ if wiki_user.user_title in ('prof','dr'):
+ subject = _('%(title)s %(last_name)s, welcome to the OSQA online community!') \
+ % {'title':wiki_user.get_user_title_display(),'last_name':wiki_user.user_last_name }
+ else:
+ subject = _('%(first_name)s, welcome to the OSQA online community!') \
+ % {'first_name':wiki_user.user_first_name}
+ from_email = settings.DEFAULT_FROM_EMAIL
+ send_mail(subject,body,from_email,[wiki_user.user_email])
+
+@transaction.commit_manually
+def signup(request):
+ #this view works through forum and mediawiki (using apache include virtual injection)
+ if request.is_include_virtual and request.REQUEST.get('was_posted','false')=='true':
+ POST_INCLUDE_VIRTUAL = True
+ POST_DATA = request.GET
+ else:
+ POST_INCLUDE_VIRTUAL = False
+ if request.method == 'POST':
+ POST_DATA = request.POST
+ else:
+ POST_DATA = None
+
+ if POST_DATA:
+ form = RegisterForm(POST_DATA)
+ if form.is_valid():
+ data = form.cleaned_data
+ login_name = data['login_name']
+ password = data['password']
+ first_name = data['first_name']
+ last_name = data['last_name']
+ screen_name = data['screen_name']
+ user_title = data['user_title']
+ email = data['email']
+ next = data['next']
+
+ #register mediawiki user
+ user_real_name = u'%s %s' % (first_name,last_name)
+ mwu = MWUser(
+ user_name=login_name,
+ user_first_name = first_name,
+ user_last_name = last_name,
+ user_title = user_title,
+ user_email = email,
+ user_real_name=user_real_name
+ )
+ mwu.set_default_options()
+ mwu.save()
+ #password may need user id so reload it
+ mwu = MWUser.objects.get(user_name = login_name)
+ mwu.set_password_and_token(password)
+ mwu.save()
+
+ #create log message
+ mwu_creation_log = Logging(
+ log_type='newusers',
+ log_action='create',
+ log_timestamp=time.strftime(MW_TS),
+ log_params=str(mwu.user_id),
+ log_namespace=2,
+ log_user=mwu,
+ log_deleted=0,
+ )
+ mwu_creation_log.save()
+ mwu_creation_log.show_in_recent_changes(ip=request.META['REMOTE_ADDR'])
+ print 'creation log saved'
+
+ #register local user
+ User.objects.create_user(screen_name, email, password)
+ u = authenticate(username=screen_name, password=password)
+ login(request,u)
+ u.mediawiki_user = mwu
+ u.save()
+
+ #save email feed settings
+ subscribe = SimpleEmailSubscribeForm(POST_DATA)
+ if subscribe.is_valid():
+ subscribe.save(user=u)
+
+ #save external login data
+ eld = ExternalLoginData(external_username=login_name, user=u)
+ eld.save()
+
+ transaction.commit()#commit so that user becomes visible on the wiki side
+
+ #check password through API and load MW HTTP header session data
+ api.check_password(login_name,password)
+
+ print 'wiki login worked'
+
+ #create welcome message on the forum
+ u.message_set.create(message=_('Welcome to the OSQA community!'))
+ print 'about to send confirmation email'
+ send_welcome_email(request, mwu, u)
+
+ if POST_INCLUDE_VIRTUAL:
+ questions = Question.objects.exclude(deleted=True, closed=True, answer_accepted=True)
+ questions = questions.order_by('-last_activity_at')[:5]
+ response = render_to_response('mediawiki/thanks_for_joining.html', \
+ {
+ 'wiki_user':mwu,
+ 'user':u,
+ 'questions':questions,
+ },
+ context_instance = RequestContext(request))
+ api.set_login_cookies(response, u)
+ #call session middleware now to get the django login cookies
+ from django.contrib.sessions.middleware import SessionMiddleware
+ sm = SessionMiddleware()
+ response = sm.process_response(request,response)
+ cookies = response.cookies
+ for c in cookies.values():
+ response.write(c.js_output())
+ else:
+ response = HttpResponseRedirect(next)
+ api.set_login_cookies(response, u)
+
+ #set cookies so that user is logged in in the wiki too
+ transaction.commit()
+ return response
+ else:
+ form = RegisterForm()
+
+ transaction.commit()
+ if request.is_include_virtual:
+ template_name = 'mediawiki/mediawiki_signup_content.html'
+ else:
+ template_name = 'mediawiki/mediawiki_signup.html'
+ return render_to_response(template_name,{'form':form},\
+ context_instance=RequestContext(request))