diff options
Diffstat (limited to 'mediawiki')
-rw-r--r-- | mediawiki/PHPSerialize.py | 149 | ||||
-rw-r--r-- | mediawiki/PHPUnserialize.py | 187 | ||||
-rw-r--r-- | mediawiki/README | 106 | ||||
-rw-r--r-- | mediawiki/UserRegister.alias.php | 7 | ||||
-rw-r--r-- | mediawiki/UserRegister.body.php | 14 | ||||
-rw-r--r-- | mediawiki/UserRegister.i18n.php | 6 | ||||
-rw-r--r-- | mediawiki/UserRegister.php | 24 | ||||
-rw-r--r-- | mediawiki/WsgiInjectableSpecialPage.php | 80 | ||||
-rw-r--r-- | mediawiki/__init__.py | 0 | ||||
-rw-r--r-- | mediawiki/api.py | 138 | ||||
-rw-r--r-- | mediawiki/auth.py | 59 | ||||
-rw-r--r-- | mediawiki/forms.py | 164 | ||||
-rw-r--r-- | mediawiki/junk.py | 2 | ||||
-rw-r--r-- | mediawiki/middleware.py | 57 | ||||
-rw-r--r-- | mediawiki/models.py | 312 | ||||
-rw-r--r-- | mediawiki/templatetags/__init__.py | 0 | ||||
-rw-r--r-- | mediawiki/templatetags/mediawikitags.py | 62 | ||||
-rw-r--r-- | mediawiki/views.py | 192 |
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)) |