Add ask01.stg and first cut at ask playbook and roles to ansible.
This commit is contained in:
parent
38c8cc5553
commit
c9e3508451
14 changed files with 1792 additions and 0 deletions
9
inventory/group_vars/ask-stg
Normal file
9
inventory/group_vars/ask-stg
Normal file
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
# Define resources for this group of hosts here.
|
||||
lvm_size: 20000
|
||||
mem_size: 2048
|
||||
num_cpus: 2
|
||||
|
||||
tcp_ports: [ 443 ]
|
||||
|
||||
fas_client_groups: sysadmin-noc,sysadmin-ask,fi-apprentice
|
10
inventory/host_vars/ask01.stg.phx2.fedoraproject.org
Normal file
10
inventory/host_vars/ask01.stg.phx2.fedoraproject.org
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
nm: 255.255.255.0
|
||||
gw: 10.5.126.254
|
||||
dns: 10.5.126.21
|
||||
ks_url: http://10.5.126.23/repo/rhel/ks/kvm-rhel-6
|
||||
ks_repo: http://10.5.126.23/repo/rhel/RHEL6-x86_64/
|
||||
volgroup: /dev/vg_guests
|
||||
eth0_ip: 10.5.126.80
|
||||
vmhost: virthost12.phx2.fedoraproject.org
|
||||
datacenter: phx2
|
50
playbooks/groups/ask.yml
Normal file
50
playbooks/groups/ask.yml
Normal file
|
@ -0,0 +1,50 @@
|
|||
- name: make ask
|
||||
hosts: ask-stg
|
||||
user: root
|
||||
gather_facts: False
|
||||
accelerate: True
|
||||
|
||||
vars_files:
|
||||
- /srv/web/infra/ansible/vars/global.yml
|
||||
- "{{ private }}/vars.yml"
|
||||
- /srv/web/infra/ansible/vars/{{ ansible_distribution }}.yml
|
||||
|
||||
tasks:
|
||||
- include: "{{ tasks }}/virt_instance_create.yml"
|
||||
- include: "{{ tasks }}/accelerate_prep.yml"
|
||||
|
||||
handlers:
|
||||
- include: "{{ handlers }}/restart_services.yml"
|
||||
|
||||
- name: make the box be real
|
||||
hosts: ask-stg
|
||||
user: root
|
||||
gather_facts: True
|
||||
accelerate: True
|
||||
|
||||
vars_files:
|
||||
- /srv/web/infra/ansible/vars/global.yml
|
||||
- "{{ private }}/vars.yml"
|
||||
- /srv/web/infra/ansible/vars/{{ ansible_distribution }}.yml
|
||||
|
||||
roles:
|
||||
- /srv/web/infra/ansible/roles/base
|
||||
- /srv/web/infra/ansible/roles/rkhunter
|
||||
- /srv/web/infra/ansible/roles/denyhosts
|
||||
- /srv/web/infra/ansible/roles/nagios_client
|
||||
- /srv/web/infra/ansible/roles/fas_client
|
||||
- /srv/web/infra/ansible/roles/ask
|
||||
|
||||
tasks:
|
||||
- include: "{{ tasks }}/hosts.yml"
|
||||
- include: "{{ tasks }}/yumrepos.yml"
|
||||
- include: "{{ tasks }}/2fa_client.yml"
|
||||
- include: "{{ tasks }}/motd.yml"
|
||||
- include: "{{ tasks }}/sudo.yml"
|
||||
- include: "{{ tasks }}/openvpn_client.yml"
|
||||
when: env != "staging"
|
||||
- include: "{{ tasks }}/apache.yml"
|
||||
- include: "{{ tasks }}/mod_wsgi.yml"
|
||||
|
||||
handlers:
|
||||
- include: "{{ handlers }}/restart_services.yml"
|
43
roles/ask/files/askbot.conf
Normal file
43
roles/ask/files/askbot.conf
Normal file
|
@ -0,0 +1,43 @@
|
|||
LoadModule deflate_module modules/mod_deflate.so
|
||||
|
||||
# If we don't do this ourselves, the askbot wsgi app will do it incorrectly
|
||||
# it seems that it doesn't evaluate the X-Forwarded-For header appropriately.
|
||||
#RedirectMatch ^/$ https://ask.stg.fedoraproject.org/questions/
|
||||
|
||||
#The below needs to be apache writable
|
||||
Alias /m/ /var/www/html/askbot/static/
|
||||
Alias /admin/media/ /usr/lib/python2.6/site-packages/django/contrib/admin/media/
|
||||
|
||||
<Directory /usr/lib/python2.6/site-packages/askbot/skins>
|
||||
Order deny,allow
|
||||
Allow from all
|
||||
</Directory>
|
||||
|
||||
WSGIDaemonProcess askbot user=apache group=apache maximum-requests=1000 display-name=askbot processes=6 threads=1 shutdown-timeout=10 python-path=/etc/askbot/sites/ask
|
||||
WSGISocketPrefix run/wsgi
|
||||
WSGIRestrictStdout On
|
||||
WSGIRestrictSignal Off
|
||||
WSGIPythonOptimize 1
|
||||
|
||||
WSGIScriptAlias / /usr/sbin/askbot.wsgi
|
||||
|
||||
|
||||
ExpiresActive On
|
||||
ExpiresByType text/css "access plus 1 week"
|
||||
ExpiresByType text/javascript "access plus 1 week"
|
||||
ExpiresByType image/png "access plus 1 week"
|
||||
ExpiresByType image/gif "access plus 1 week"
|
||||
|
||||
<Location />
|
||||
SetOutputFilter DEFLATE
|
||||
WSGIProcessGroup askbot
|
||||
Order deny,allow
|
||||
Allow from all
|
||||
</Location>
|
||||
|
||||
Alias /upfiles/ /var/lib/askbot/upfiles/ask/
|
||||
|
||||
<Directory /var/lib/askbot/upfiles/ask>
|
||||
Order deny,allow
|
||||
Allow from all
|
||||
</Directory>
|
16
roles/ask/files/askbot.wsgi
Normal file
16
roles/ask/files/askbot.wsgi
Normal file
|
@ -0,0 +1,16 @@
|
|||
#!/usr/bin/python
|
||||
import os
|
||||
import sys
|
||||
sys.stdout = sys.stderr
|
||||
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'config.settings'
|
||||
|
||||
# Here we have to trick askbot into thinking its using ssl so it
|
||||
# produces the correct URLs for openid/auth stuff (mostly for stg).
|
||||
os.environ['HTTPS'] = "on"
|
||||
def is_secure(self):
|
||||
return True
|
||||
import django.core.handlers.wsgi
|
||||
django.core.handlers.wsgi.WSGIRequest.is_secure = is_secure
|
||||
|
||||
application = django.core.handlers.wsgi.WSGIHandler()
|
42
roles/ask/files/backends.py
Normal file
42
roles/ask/files/backends.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
from django.core.mail.backends.base import BaseEmailBackend
|
||||
|
||||
from .models import Email, PRIORITY, STATUS
|
||||
|
||||
|
||||
class EmailBackend(BaseEmailBackend):
|
||||
|
||||
def open(self):
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def send_messages(self, email_messages):
|
||||
"""
|
||||
Queue one or more EmailMessage objects and returns the number of
|
||||
email messages sent.
|
||||
"""
|
||||
if not email_messages:
|
||||
return
|
||||
num_sent = 0
|
||||
|
||||
for email in email_messages:
|
||||
num_sent += 1
|
||||
subject = email.subject
|
||||
from_email = email.from_email
|
||||
message = email.body
|
||||
|
||||
# Check whether email has 'text/html' alternative
|
||||
alternatives = getattr(email, 'alternatives', ())
|
||||
for alternative in alternatives:
|
||||
if alternative[1] == 'text/html':
|
||||
html_message = alternative[0]
|
||||
break
|
||||
else:
|
||||
html_message = ''
|
||||
|
||||
for recipient in email.to:
|
||||
Email.objects.create(from_email=from_email, to=recipient,
|
||||
subject=subject, html_message=html_message,
|
||||
message=message, status=STATUS.queued,
|
||||
priority=PRIORITY.medium)
|
1
roles/ask/files/cron-ask-send-reminders
Normal file
1
roles/ask/files/cron-ask-send-reminders
Normal file
|
@ -0,0 +1 @@
|
|||
0 0 * * * root /usr/bin/python /etc/askbot/sites/ask/config/manage.py send_accept_answer_reminders > /dev/null 2> /dev/null
|
2
roles/ask/files/cron-post-office-send-mail
Normal file
2
roles/ask/files/cron-post-office-send-mail
Normal file
|
@ -0,0 +1,2 @@
|
|||
0 * * * * root /usr/bin/python /etc/askbot/sites/ask/config/manage.py send_queued_mail > /dev/null 2> /dev/null
|
||||
|
BIN
roles/ask/files/fedora-openid.png
Normal file
BIN
roles/ask/files/fedora-openid.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
108
roles/ask/files/login_providers.py
Normal file
108
roles/ask/files/login_providers.py
Normal file
|
@ -0,0 +1,108 @@
|
|||
"""
|
||||
External service key settings
|
||||
"""
|
||||
from askbot.conf.settings_wrapper import settings
|
||||
from askbot.conf.super_groups import LOGIN_USERS_COMMUNICATION
|
||||
from askbot.deps import livesettings
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings as django_settings
|
||||
from askbot.skins import utils as skin_utils
|
||||
|
||||
LOGIN_PROVIDERS = livesettings.ConfigurationGroup(
|
||||
'LOGIN_PROVIDERS',
|
||||
_('Login provider setings'),
|
||||
super_group = LOGIN_USERS_COMMUNICATION
|
||||
)
|
||||
|
||||
settings.register(
|
||||
livesettings.BooleanValue(
|
||||
LOGIN_PROVIDERS,
|
||||
'PASSWORD_REGISTER_SHOW_PROVIDER_BUTTONS',
|
||||
default = True,
|
||||
description=_('Show alternative login provider buttons on the password "Sign Up" page'),
|
||||
)
|
||||
)
|
||||
|
||||
settings.register(
|
||||
livesettings.BooleanValue(
|
||||
LOGIN_PROVIDERS,
|
||||
'SIGNIN_ALWAYS_SHOW_LOCAL_LOGIN',
|
||||
default = True,
|
||||
description=_('Always display local login form and hide "Askbot" button.'),
|
||||
)
|
||||
)
|
||||
|
||||
settings.register(
|
||||
livesettings.BooleanValue(
|
||||
LOGIN_PROVIDERS,
|
||||
'SIGNIN_WORDPRESS_SITE_ENABLED',
|
||||
default = False,
|
||||
description=_('Activate to allow login with self-hosted wordpress site'),
|
||||
help_text=_('to activate this feature you must fill out the wordpress xml-rpc setting bellow')
|
||||
)
|
||||
)
|
||||
|
||||
settings.register(
|
||||
livesettings.URLValue(
|
||||
LOGIN_PROVIDERS,
|
||||
'WORDPRESS_SITE_URL',
|
||||
default = '',
|
||||
description=_('Fill it with the wordpress url to the xml-rpc, normally http://mysite.com/xmlrpc.php'),
|
||||
help_text=_('To enable, go to Settings->Writing->Remote Publishing and check the box for XML-RPC')
|
||||
)
|
||||
)
|
||||
|
||||
settings.register(
|
||||
livesettings.ImageValue(
|
||||
LOGIN_PROVIDERS,
|
||||
'WORDPRESS_SITE_ICON',
|
||||
default='/images/logo.gif',
|
||||
description=_('Upload your icon'),
|
||||
url_resolver=skin_utils.get_media_url
|
||||
)
|
||||
)
|
||||
|
||||
providers = (
|
||||
'local',
|
||||
'AOL',
|
||||
'FAS-OpenID',
|
||||
'Blogger',
|
||||
'ClaimID',
|
||||
'Facebook',
|
||||
'Flickr',
|
||||
'Google',
|
||||
'Twitter',
|
||||
'LinkedIn',
|
||||
'LiveJournal',
|
||||
#'myOpenID',
|
||||
'OpenID',
|
||||
'Technorati',
|
||||
'Wordpress',
|
||||
'Vidoop',
|
||||
'Verisign',
|
||||
'Yahoo',
|
||||
'identi.ca',
|
||||
)
|
||||
|
||||
need_extra_setup = ('Twitter', 'Facebook', 'LinkedIn', 'identi.ca',)
|
||||
|
||||
for provider in providers:
|
||||
kwargs = {
|
||||
'description': _('Activate %(provider)s login') % {'provider': provider},
|
||||
'default': True,
|
||||
}
|
||||
if provider in need_extra_setup:
|
||||
kwargs['help_text'] = _(
|
||||
'Note: to really enable %(provider)s login '
|
||||
'some additional parameters will need to be set '
|
||||
'in the "External keys" section'
|
||||
) % {'provider': provider}
|
||||
|
||||
setting_name = 'SIGNIN_%s_ENABLED' % provider.upper()
|
||||
settings.register(
|
||||
livesettings.BooleanValue(
|
||||
LOGIN_PROVIDERS,
|
||||
setting_name,
|
||||
**kwargs
|
||||
)
|
||||
)
|
195
roles/ask/files/sanction-client.py
Normal file
195
roles/ask/files/sanction-client.py
Normal file
|
@ -0,0 +1,195 @@
|
|||
# vim: set ts=4 sw=)
|
||||
""" OAuth 2.0 client librar
|
||||
"""
|
||||
|
||||
from json import loads
|
||||
from datetime import datetime, timedelta
|
||||
from time import mktime
|
||||
try:
|
||||
from urllib import urlencode
|
||||
from urllib2 import Request, urlopen
|
||||
from urlparse import urlsplit, urlunsplit, parse_qsl
|
||||
|
||||
# monkeypatch httpmessage
|
||||
from httplib import HTTPMessage
|
||||
def get_charset(self):
|
||||
try:
|
||||
data = filter(lambda s: 'Content-Type' in s, self.headers)[0]
|
||||
if 'charset' in data:
|
||||
cs = data[data.index(';') + 1:-2].split('=')[1].lower()
|
||||
return cs
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
return 'utf-8'
|
||||
HTTPMessage.get_content_charset = get_charset
|
||||
except ImportError:
|
||||
from urllib.parse import urlencode, urlsplit, urlunsplit, parse_qsl
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
|
||||
class Client(object):
|
||||
""" OAuth 2.0 client object
|
||||
"""
|
||||
|
||||
def __init__(self, auth_endpoint=None, token_endpoint=None,
|
||||
resource_endpoint=None, client_id=None, client_secret=None,
|
||||
redirect_uri=None, token_transport=None):
|
||||
assert(hasattr(token_transport, '__call__') or
|
||||
token_transport in ('headers', 'query', None))
|
||||
|
||||
self.auth_endpoint = auth_endpoint
|
||||
self.token_endpoint = token_endpoint
|
||||
self.resource_endpoint = resource_endpoint
|
||||
self.redirect_uri = redirect_uri
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
self.access_token = None
|
||||
self.token_transport = token_transport or 'query'
|
||||
self.token_expires = -1
|
||||
self.refresh_token = None
|
||||
|
||||
def auth_uri(self, scope=None, scope_delim=None, state=None, **kwargs):
|
||||
""" Builds the auth URI for the authorization endpoint
|
||||
"""
|
||||
scope_delim = scope_delim and scope_delim or ' '
|
||||
kwargs.update({
|
||||
'client_id': self.client_id,
|
||||
'response_type': 'code',
|
||||
})
|
||||
|
||||
if scope is not None:
|
||||
kwargs['scope'] = scope_delim.join(scope)
|
||||
|
||||
if state is not None:
|
||||
kwargs['state'] = state
|
||||
|
||||
if self.redirect_uri is not None:
|
||||
kwargs['redirect_uri'] = self.redirect_uri
|
||||
|
||||
return '%s?%s' % (self.auth_endpoint, urlencode(kwargs))
|
||||
|
||||
def request_token(self, parser=None, exclude=None, **kwargs):
|
||||
""" Request an access token from the token endpoint.
|
||||
This is largely a helper method and expects the client code to
|
||||
understand what the server expects. Anything that's passed into
|
||||
``**kwargs`` will be sent (``urlencode``d) to the endpoint. Client
|
||||
secret and client ID are automatically included, so are not required
|
||||
as kwargs. For example::
|
||||
|
||||
# if requesting access token from auth flow:
|
||||
{
|
||||
'code': rval_from_auth,
|
||||
}
|
||||
|
||||
# if refreshing access token:
|
||||
{
|
||||
'refresh_token': stored_refresh_token,
|
||||
'grant_type': 'refresh_token',
|
||||
}
|
||||
|
||||
:param exclude: An iterable of fields to exclude from the ``POST``
|
||||
data. This is useful for fields such as ``redirect_uri``
|
||||
that are required during initial code/token exchange,
|
||||
but will cause errors with some providers when
|
||||
exchanging refresh tokens for new access tokens.
|
||||
:param parser: Callback to deal with returned data. Not all providers
|
||||
use JSON.
|
||||
"""
|
||||
kwargs = kwargs and kwargs or {}
|
||||
exclude = exclude or {}
|
||||
|
||||
parser = parser and parser or loads
|
||||
kwargs.update({
|
||||
'client_id': self.client_id,
|
||||
'client_secret': self.client_secret,
|
||||
'grant_type': 'grant_type' in kwargs and kwargs['grant_type'] or \
|
||||
'authorization_code'
|
||||
})
|
||||
if self.redirect_uri is not None and 'redirect_uri' not in exclude:
|
||||
kwargs.update({'redirect_uri': self.redirect_uri})
|
||||
|
||||
msg = urlopen(self.token_endpoint, urlencode(kwargs).encode(
|
||||
'utf-8'))
|
||||
data = parser(msg.read().decode(msg.info().get_content_charset() or
|
||||
'utf-8'))
|
||||
|
||||
for key in data:
|
||||
setattr(self, key, data[key])
|
||||
|
||||
# expires_in is RFC-compliant. if anything else is used by the
|
||||
# provider, token_expires must be set manually
|
||||
if hasattr(self, 'expires_in'):
|
||||
self.token_expires = mktime((datetime.utcnow() + timedelta(
|
||||
seconds=self.expires_in)).timetuple())
|
||||
|
||||
assert(self.access_token is not None)
|
||||
|
||||
def refresh(self):
|
||||
assert self.refresh_token is not None
|
||||
self.request_token(refresh_token=self.refresh_token,
|
||||
grant_type='refresh_token', exclude=('redirect_uri',))
|
||||
|
||||
def request(self, url, method=None, data=None, parser=None):
|
||||
""" Request user data from the resource endpoint
|
||||
:param url: The path to the resource and querystring if required
|
||||
:param method: HTTP method. Defaults to ``GET`` unless data is not None
|
||||
in which case it defaults to ``POST``
|
||||
:param data: Data to be POSTed to the resource endpoint
|
||||
:param parser: Parser callback to deal with the returned data. Defaults
|
||||
to ``json.loads`.`
|
||||
"""
|
||||
assert(self.access_token is not None)
|
||||
parser = parser or loads
|
||||
|
||||
if not method:
|
||||
method = 'GET' if not data else 'POST'
|
||||
|
||||
if not hasattr(self.token_transport, '__call__'):
|
||||
transport = globals()['_transport_{0}'.format(self.token_transport)]
|
||||
else:
|
||||
transport = self.token_transport
|
||||
|
||||
req = transport('{0}{1}'.format(self.resource_endpoint,
|
||||
url), self.access_token, data=data, method=method)
|
||||
|
||||
resp = urlopen(req)
|
||||
data = resp.read()
|
||||
try:
|
||||
# try to decode it first using either the content charset, falling
|
||||
# back to utf8
|
||||
return parser(data.decode(resp.info().get_content_charset() or
|
||||
'utf-8'))
|
||||
except UnicodeDecodeError:
|
||||
# if we've gotten a decoder error, the calling code better know how
|
||||
# to deal with it. some providers (i.e. stackexchange) like to gzip
|
||||
# their responses, so this allows the client code to handle it
|
||||
# directly.
|
||||
return parser(data)
|
||||
|
||||
def _transport_headers(url, access_token, data=None, method=None):
|
||||
try:
|
||||
req = Request(url, data=data, method=method)
|
||||
except TypeError:
|
||||
req = Request(url, data=data)
|
||||
req.get_method = lambda: method
|
||||
|
||||
req.headers.update({
|
||||
'Authorization': 'Bearer {0}'.format(access_token)
|
||||
})
|
||||
return req
|
||||
|
||||
def _transport_query(url, access_token, data=None, method=None):
|
||||
parts = urlsplit(url)
|
||||
query = dict(parse_qsl(parts.query))
|
||||
query.update({
|
||||
'access_token': access_token
|
||||
})
|
||||
url = urlunsplit((parts.scheme, parts.netloc, parts.path,
|
||||
urlencode(query), parts.fragment))
|
||||
try:
|
||||
req = Request(url, data=data, method=method)
|
||||
except TypeError:
|
||||
req = Request(url, data=data)
|
||||
req.get_method = lambda: method
|
||||
return req
|
839
roles/ask/files/util.py
Normal file
839
roles/ask/files/util.py
Normal file
|
@ -0,0 +1,839 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import cgi
|
||||
import urllib
|
||||
import urlparse
|
||||
import functools
|
||||
import re
|
||||
import random
|
||||
from openid.store.interface import OpenIDStore
|
||||
from openid.association import Association as OIDAssociation
|
||||
from openid.extensions import sreg
|
||||
from openid import store as openid_store
|
||||
import oauth2 as oauth # OAuth1 protocol
|
||||
|
||||
from django.db.models.query import Q
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils import simplejson
|
||||
from django.utils.datastructures import SortedDict
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
try:
|
||||
from hashlib import md5
|
||||
except:
|
||||
from md5 import md5
|
||||
|
||||
from askbot.conf import settings as askbot_settings
|
||||
|
||||
# needed for some linux distributions like debian
|
||||
try:
|
||||
from openid.yadis import xri
|
||||
except:
|
||||
from yadis import xri
|
||||
|
||||
import time, base64, hmac, hashlib, operator, logging
|
||||
from models import Association, Nonce
|
||||
|
||||
__all__ = ['OpenID', 'DjangoOpenIDStore', 'from_openid_response', 'clean_next']
|
||||
|
||||
ALLOWED_LOGIN_TYPES = ('password', 'oauth', 'openid-direct', 'openid-username', 'wordpress')
|
||||
|
||||
class OpenID:
|
||||
def __init__(self, openid_, issued, attrs=None, sreg_=None):
|
||||
logging.debug('init janrain openid object')
|
||||
self.openid = openid_
|
||||
self.issued = issued
|
||||
self.attrs = attrs or {}
|
||||
self.sreg = sreg_ or {}
|
||||
self.is_iname = (xri.identifierScheme(openid_) == 'XRI')
|
||||
|
||||
def __repr__(self):
|
||||
return '<OpenID: %s>' % self.openid
|
||||
|
||||
def __str__(self):
|
||||
return self.openid
|
||||
|
||||
class DjangoOpenIDStore(OpenIDStore):
|
||||
def __init__(self):
|
||||
self.max_nonce_age = 6 * 60 * 60 # Six hours
|
||||
|
||||
def storeAssociation(self, server_url, association):
|
||||
assoc = Association(
|
||||
server_url = server_url,
|
||||
handle = association.handle,
|
||||
secret = base64.encodestring(association.secret),
|
||||
issued = association.issued,
|
||||
lifetime = association.issued,
|
||||
assoc_type = association.assoc_type
|
||||
)
|
||||
assoc.save()
|
||||
|
||||
def getAssociation(self, server_url, handle=None):
|
||||
assocs = []
|
||||
if handle is not None:
|
||||
assocs = Association.objects.filter(
|
||||
server_url = server_url, handle = handle
|
||||
)
|
||||
else:
|
||||
assocs = Association.objects.filter(
|
||||
server_url = server_url
|
||||
)
|
||||
if not assocs:
|
||||
return None
|
||||
associations = []
|
||||
for assoc in assocs:
|
||||
association = OIDAssociation(
|
||||
assoc.handle, base64.decodestring(assoc.secret), assoc.issued,
|
||||
assoc.lifetime, assoc.assoc_type
|
||||
)
|
||||
if association.getExpiresIn() == 0:
|
||||
self.removeAssociation(server_url, assoc.handle)
|
||||
else:
|
||||
associations.append((association.issued, association))
|
||||
if not associations:
|
||||
return None
|
||||
return associations[-1][1]
|
||||
|
||||
def removeAssociation(self, server_url, handle):
|
||||
assocs = list(Association.objects.filter(
|
||||
server_url = server_url, handle = handle
|
||||
))
|
||||
assocs_exist = len(assocs) > 0
|
||||
for assoc in assocs:
|
||||
assoc.delete()
|
||||
return assocs_exist
|
||||
|
||||
def useNonce(self, server_url, timestamp, salt):
|
||||
if abs(timestamp - time.time()) > openid_store.nonce.SKEW:
|
||||
return False
|
||||
|
||||
query = [
|
||||
Q(server_url__exact=server_url),
|
||||
Q(timestamp__exact=timestamp),
|
||||
Q(salt__exact=salt),
|
||||
]
|
||||
try:
|
||||
ononce = Nonce.objects.get(reduce(operator.and_, query))
|
||||
except Nonce.DoesNotExist:
|
||||
ononce = Nonce(
|
||||
server_url=server_url,
|
||||
timestamp=timestamp,
|
||||
salt=salt
|
||||
)
|
||||
ononce.save()
|
||||
return True
|
||||
|
||||
ononce.delete()
|
||||
|
||||
return False
|
||||
|
||||
def cleanupAssociations(self):
|
||||
Association.objects.extra(where=['issued + lifetimeint<(%s)' % time.time()]).delete()
|
||||
|
||||
def getAuthKey(self):
|
||||
# Use first AUTH_KEY_LEN characters of md5 hash of SECRET_KEY
|
||||
return hashlib.md5(settings.SECRET_KEY).hexdigest()[:self.AUTH_KEY_LEN]
|
||||
|
||||
def isDumb(self):
|
||||
return False
|
||||
|
||||
def from_openid_response(openid_response):
|
||||
""" return openid object from response """
|
||||
issued = int(time.time())
|
||||
sreg_resp = sreg.SRegResponse.fromSuccessResponse(openid_response) \
|
||||
or []
|
||||
|
||||
return OpenID(
|
||||
openid_response.identity_url, issued, openid_response.signed_fields,
|
||||
dict(sreg_resp)
|
||||
)
|
||||
|
||||
def get_provider_name(openid_url):
|
||||
"""returns provider name from the openid_url
|
||||
"""
|
||||
openid_str = openid_url
|
||||
bits = openid_str.split('/')
|
||||
base_url = bits[2] #assume this is base url
|
||||
url_bits = base_url.split('.')
|
||||
return url_bits[-2].lower()
|
||||
|
||||
def use_password_login():
|
||||
"""password login is activated
|
||||
either if USE_RECAPTCHA is false
|
||||
of if recaptcha keys are set correctly
|
||||
|
||||
Currently hotfixed to disable this
|
||||
"""
|
||||
return False
|
||||
if askbot_settings.SIGNIN_WORDPRESS_SITE_ENABLED:
|
||||
return True
|
||||
if askbot_settings.USE_RECAPTCHA:
|
||||
if askbot_settings.RECAPTCHA_KEY and askbot_settings.RECAPTCHA_SECRET:
|
||||
return True
|
||||
else:
|
||||
logging.critical('if USE_RECAPTCHA == True, set recaptcha keys!!!')
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def filter_enabled_providers(data):
|
||||
"""deletes data about disabled providers from
|
||||
the input dictionary
|
||||
"""
|
||||
delete_list = list()
|
||||
for provider_key, provider_settings in data.items():
|
||||
name = provider_settings['name']
|
||||
if name == 'fasopenid':
|
||||
is_enabled = True
|
||||
else:
|
||||
is_enabled = getattr(askbot_settings, 'SIGNIN_' + name.upper() + '_ENABLED')
|
||||
if is_enabled == False:
|
||||
delete_list.append(provider_key)
|
||||
|
||||
for provider_key in delete_list:
|
||||
del data[provider_key]
|
||||
|
||||
return data
|
||||
|
||||
class LoginMethod(object):
|
||||
"""Helper class to add custom authentication modules
|
||||
as plugins for the askbot's version of django_authopenid
|
||||
"""
|
||||
def __init__(self, login_module_path):
|
||||
from askbot.utils.loading import load_module
|
||||
self.mod = load_module(login_module_path)
|
||||
self.mod_path = login_module_path
|
||||
self.read_params()
|
||||
|
||||
def get_required_attr(self, attr_name, required_for_what):
|
||||
attr_value = getattr(self.mod, attr_name, None)
|
||||
if attr_value is None:
|
||||
raise ImproperlyConfigured(
|
||||
'%s.%s is required for %s' % (
|
||||
self.mod_path,
|
||||
attr_name,
|
||||
required_for_what
|
||||
)
|
||||
)
|
||||
return attr_value
|
||||
|
||||
def read_params(self):
|
||||
self.is_major = getattr(self.mod, 'BIG_BUTTON', True)
|
||||
if not isinstance(self.is_major, bool):
|
||||
raise ImproperlyConfigured(
|
||||
'Boolean value expected for %s.BIG_BUTTON' % self.mod_path
|
||||
)
|
||||
|
||||
self.order_number = getattr(self.mod, 'ORDER_NUMBER', 1)
|
||||
if not isinstance(self.order_number, int):
|
||||
raise ImproperlyConfigured(
|
||||
'Integer value expected for %s.ORDER_NUMBER' % self.mod_path
|
||||
)
|
||||
|
||||
self.name = getattr(self.mod, 'NAME', None)
|
||||
if self.name is None or not isinstance(self.name, basestring):
|
||||
raise ImproperlyConfigured(
|
||||
'%s.NAME is required as a string parameter' % self.mod_path
|
||||
)
|
||||
if not re.search(r'^[a-zA-Z0-9]+$', self.name):
|
||||
raise ImproperlyConfigured(
|
||||
'%s.NAME must be a string of ASCII letters and digits only'
|
||||
)
|
||||
|
||||
self.display_name = getattr(self.mod, 'DISPLAY_NAME', None)
|
||||
if self.display_name is None or not isinstance(self.display_name, basestring):
|
||||
raise ImproperlyConfigured(
|
||||
'%s.DISPLAY_NAME is required as a string parameter' % self.mod_path
|
||||
)
|
||||
self.extra_token_name = getattr(self.mod, 'EXTRA_TOKEN_NAME', None)
|
||||
self.login_type = getattr(self.mod, 'TYPE', None)
|
||||
if self.login_type is None or self.login_type not in ALLOWED_LOGIN_TYPES:
|
||||
raise ImproperlyConfigured(
|
||||
"%s.TYPE must be a string "
|
||||
"and the possible values are : 'password', 'oauth', "
|
||||
"'openid-direct', 'openid-username'." % self.mod_path
|
||||
)
|
||||
self.icon_media_path = getattr(self.mod, 'ICON_MEDIA_PATH', None)
|
||||
if self.icon_media_path is None:
|
||||
raise ImproperlyConfigured(
|
||||
'%s.ICON_MEDIA_PATH is required and must be a url '
|
||||
'to the image used as login button' % self.mod_path
|
||||
)
|
||||
|
||||
self.create_password_prompt = getattr(self.mod, 'CREATE_PASSWORD_PROMPT', None)
|
||||
self.change_password_prompt = getattr(self.mod, 'CHANGE_PASSWORD_PROMPT', None)
|
||||
|
||||
if self.login_type == 'password':
|
||||
self.check_password_function = self.get_required_attr(
|
||||
'check_password',
|
||||
'custom password login'
|
||||
)
|
||||
if self.login_type == 'oauth':
|
||||
for_what = 'custom OAuth login'
|
||||
self.oauth_consumer_key = self.get_required_attr('OAUTH_CONSUMER_KEY', for_what)
|
||||
self.oauth_consumer_secret = self.get_required_attr('OAUTH_CONSUMER_SECRET', for_what)
|
||||
self.oauth_request_token_url = self.get_required_attr('OAUTH_REQUEST_TOKEN_URL', for_what)
|
||||
self.oauth_access_token_url = self.get_required_attr('OAUTH_ACCESS_TOKEN_URL', for_what)
|
||||
self.oauth_authorize_url = self.get_required_attr('OAUTH_AUTHORIZE_URL', for_what)
|
||||
self.oauth_get_user_id_function = self.get_required_attr('oauth_get_user_id_function', for_what)
|
||||
|
||||
if self.login_type.startswith('openid'):
|
||||
self.openid_endpoint = self.get_required_attr('OPENID_ENDPOINT', 'custom OpenID login')
|
||||
if self.login_type == 'openid-username':
|
||||
if '%(username)s' not in self.openid_endpoint:
|
||||
msg = 'If OpenID provider requires a username, ' + \
|
||||
'then value of %s.OPENID_ENDPOINT must contain ' + \
|
||||
'%(username)s so that the username can be transmitted to the provider'
|
||||
raise ImproperlyConfigured(msg % self.mod_path)
|
||||
|
||||
self.tooltip_text = getattr(self.mod, 'TOOLTIP_TEXT', None)
|
||||
|
||||
def as_dict(self):
|
||||
"""returns parameters as dictionary that
|
||||
can be inserted into one of the provider data dictionaries
|
||||
for the use in the UI"""
|
||||
params = (
|
||||
'name', 'display_name', 'type', 'icon_media_path',
|
||||
'extra_token_name', 'create_password_prompt',
|
||||
'change_password_prompt', 'consumer_key', 'consumer_secret',
|
||||
'request_token_url', 'access_token_url', 'authorize_url',
|
||||
'get_user_id_function', 'openid_endpoint', 'tooltip_text',
|
||||
'check_password',
|
||||
)
|
||||
#some parameters in the class have different names from those
|
||||
#in the dictionary
|
||||
parameter_map = {
|
||||
'type': 'login_type',
|
||||
'consumer_key': 'oauth_consumer_key',
|
||||
'consumer_secret': 'oauth_consumer_secret',
|
||||
'request_token_url': 'oauth_request_token_url',
|
||||
'access_token_url': 'oauth_access_token_url',
|
||||
'authorize_url': 'oauth_authorize_url',
|
||||
'get_user_id_function': 'oauth_get_user_id_function',
|
||||
'check_password': 'check_password_function'
|
||||
}
|
||||
data = dict()
|
||||
for param in params:
|
||||
attr_name = parameter_map.get(param, param)
|
||||
data[param] = getattr(self, attr_name, None)
|
||||
if self.login_type == 'password':
|
||||
#passwords in external login systems are not changeable
|
||||
data['password_changeable'] = False
|
||||
return data
|
||||
|
||||
def add_custom_provider(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper():
|
||||
providers = func()
|
||||
login_module_path = getattr(settings, 'ASKBOT_CUSTOM_AUTH_MODULE', None)
|
||||
if login_module_path:
|
||||
mod = LoginMethod(login_module_path)
|
||||
if mod.is_major != func.is_major:
|
||||
return providers#only patch the matching provider set
|
||||
providers.insert(mod.order_number - 1, mod.name, mod.as_dict())
|
||||
return providers
|
||||
return wrapper
|
||||
|
||||
def get_enabled_major_login_providers():
|
||||
"""returns a dictionary with data about login providers
|
||||
whose icons are to be shown in large format
|
||||
|
||||
disabled providers are excluded
|
||||
|
||||
items of the dictionary are dictionaries with keys:
|
||||
|
||||
* name
|
||||
* display_name
|
||||
* icon_media_path (relative to /media directory)
|
||||
* type (oauth|openid-direct|openid-generic|openid-username|password)
|
||||
|
||||
Fields dependent on type of the login provider type
|
||||
---------------------------------------------------
|
||||
|
||||
Password (type = password) - login provider using login name and password:
|
||||
|
||||
* extra_token_name - a phrase describing what the login name and the
|
||||
password are from
|
||||
* create_password_prompt - a phrase prompting to create an account
|
||||
* change_password_prompt - a phrase prompting to change password
|
||||
|
||||
OpenID (type = openid) - Provider of login using the OpenID protocol
|
||||
|
||||
* openid_endpoint (required for type=openid|openid-username)
|
||||
for type openid-username - the string must have %(username)s
|
||||
format variable, plain string url otherwise
|
||||
* extra_token_name - required for type=openid-username
|
||||
describes name of required extra token - e.g. "XYZ user name"
|
||||
|
||||
OAuth2 (type = oauth)
|
||||
|
||||
* request_token_url - url to initiate OAuth2 protocol with the resource
|
||||
* access_token_url - url to access users data on the resource via OAuth2
|
||||
* authorize_url - url at which user can authorize the app to access a resource
|
||||
* authenticate_url - url to authenticate user (lower privilege than authorize)
|
||||
* get_user_id_function - a function that returns user id from data dictionary
|
||||
containing: response to the access token url & consumer_key
|
||||
and consumer secret. The purpose of this function is to hide the differences
|
||||
between the ways user id is accessed from the different OAuth providers
|
||||
"""
|
||||
data = SortedDict()
|
||||
|
||||
if use_password_login():
|
||||
site_name = askbot_settings.APP_SHORT_NAME
|
||||
prompt = _('%(site)s user name and password') % {'site': site_name}
|
||||
data['local'] = {
|
||||
'name': 'local',
|
||||
'display_name': site_name,
|
||||
'extra_token_name': prompt,
|
||||
'type': 'password',
|
||||
'create_password_prompt': _('Create a password-protected account'),
|
||||
'change_password_prompt': _('Change your password'),
|
||||
'icon_media_path': askbot_settings.LOCAL_LOGIN_ICON,
|
||||
'password_changeable': True
|
||||
}
|
||||
|
||||
data['fasopenid'] = {
|
||||
'name': 'fasopenid',
|
||||
'display_name': 'FAS-OpenID',
|
||||
'type': 'openid-direct',
|
||||
'icon_media_path': '/jquery-openid/images/fedora-openid.png',
|
||||
'openid_endpoint': 'http://id.fedoraproject.org/',
|
||||
}
|
||||
|
||||
|
||||
def get_facebook_user_id(client):
|
||||
"""returns facebook user id given the access token"""
|
||||
profile = client.request('me')
|
||||
return profile['id']
|
||||
|
||||
if askbot_settings.FACEBOOK_KEY and askbot_settings.FACEBOOK_SECRET:
|
||||
data['facebook'] = {
|
||||
'name': 'facebook',
|
||||
'display_name': 'Facebook',
|
||||
'type': 'oauth2',
|
||||
'auth_endpoint': 'https://www.facebook.com/dialog/oauth/',
|
||||
'token_endpoint': 'https://graph.facebook.com/oauth/access_token',
|
||||
'resource_endpoint': 'https://graph.facebook.com/',
|
||||
'icon_media_path': '/jquery-openid/images/facebook.gif',
|
||||
'get_user_id_function': get_facebook_user_id,
|
||||
'response_parser': lambda data: dict(urlparse.parse_qsl(data))
|
||||
|
||||
}
|
||||
if askbot_settings.TWITTER_KEY and askbot_settings.TWITTER_SECRET:
|
||||
data['twitter'] = {
|
||||
'name': 'twitter',
|
||||
'display_name': 'Twitter',
|
||||
'type': 'oauth',
|
||||
'request_token_url': 'https://api.twitter.com/oauth/request_token',
|
||||
'access_token_url': 'https://api.twitter.com/oauth/access_token',
|
||||
'authorize_url': 'https://api.twitter.com/oauth/authorize',
|
||||
'authenticate_url': 'https://api.twitter.com/oauth/authenticate',
|
||||
'get_user_id_url': 'https://twitter.com/account/verify_credentials.json',
|
||||
'icon_media_path': '/jquery-openid/images/twitter.gif',
|
||||
'get_user_id_function': lambda data: data['user_id'],
|
||||
}
|
||||
def get_identica_user_id(data):
|
||||
consumer = oauth.Consumer(data['consumer_key'], data['consumer_secret'])
|
||||
token = oauth.Token(data['oauth_token'], data['oauth_token_secret'])
|
||||
client = oauth.Client(consumer, token=token)
|
||||
url = 'https://identi.ca/api/account/verify_credentials.json'
|
||||
response, content = client.request(url, 'GET')
|
||||
json = simplejson.loads(content)
|
||||
return json['id']
|
||||
if askbot_settings.IDENTICA_KEY and askbot_settings.IDENTICA_SECRET:
|
||||
data['identi.ca'] = {
|
||||
'name': 'identi.ca',
|
||||
'display_name': 'identi.ca',
|
||||
'type': 'oauth',
|
||||
'request_token_url': 'https://identi.ca/api/oauth/request_token',
|
||||
'access_token_url': 'https://identi.ca/api/oauth/access_token',
|
||||
'authorize_url': 'https://identi.ca/api/oauth/authorize',
|
||||
'authenticate_url': 'https://identi.ca/api/oauth/authorize',
|
||||
'icon_media_path': '/jquery-openid/images/identica.png',
|
||||
'get_user_id_function': get_identica_user_id,
|
||||
}
|
||||
def get_linked_in_user_id(data):
|
||||
consumer = oauth.Consumer(data['consumer_key'], data['consumer_secret'])
|
||||
token = oauth.Token(data['oauth_token'], data['oauth_token_secret'])
|
||||
client = oauth.Client(consumer, token=token)
|
||||
url = 'https://api.linkedin.com/v1/people/~:(first-name,last-name,id)'
|
||||
response, content = client.request(url, 'GET')
|
||||
if response['status'] == '200':
|
||||
id_re = re.compile(r'<id>([^<]+)</id>')
|
||||
matches = id_re.search(content)
|
||||
if matches:
|
||||
return matches.group(1)
|
||||
raise OAuthError()
|
||||
|
||||
if askbot_settings.SIGNIN_WORDPRESS_SITE_ENABLED and askbot_settings.WORDPRESS_SITE_URL:
|
||||
data['wordpress_site'] = {
|
||||
'name': 'wordpress_site',
|
||||
'display_name': 'Self hosted wordpress blog', #need to be added as setting.
|
||||
'icon_media_path': askbot_settings.WORDPRESS_SITE_ICON,
|
||||
'type': 'wordpress_site',
|
||||
}
|
||||
if askbot_settings.LINKEDIN_KEY and askbot_settings.LINKEDIN_SECRET:
|
||||
data['linkedin'] = {
|
||||
'name': 'linkedin',
|
||||
'display_name': 'LinkedIn',
|
||||
'type': 'oauth',
|
||||
'request_token_url': 'https://api.linkedin.com/uas/oauth/requestToken',
|
||||
'access_token_url': 'https://api.linkedin.com/uas/oauth/accessToken',
|
||||
'authorize_url': 'https://www.linkedin.com/uas/oauth/authorize',
|
||||
'authenticate_url': 'https://www.linkedin.com/uas/oauth/authenticate',
|
||||
'icon_media_path': '/jquery-openid/images/linkedin.gif',
|
||||
'get_user_id_function': get_linked_in_user_id
|
||||
}
|
||||
data['google'] = {
|
||||
'name': 'google',
|
||||
'display_name': 'Google',
|
||||
'type': 'openid-direct',
|
||||
'icon_media_path': '/jquery-openid/images/google.gif',
|
||||
'openid_endpoint': 'https://www.google.com/accounts/o8/id',
|
||||
}
|
||||
data['yahoo'] = {
|
||||
'name': 'yahoo',
|
||||
'display_name': 'Yahoo',
|
||||
'type': 'openid-direct',
|
||||
'icon_media_path': '/jquery-openid/images/yahoo.gif',
|
||||
'tooltip_text': _('Sign in with Yahoo'),
|
||||
'openid_endpoint': 'http://yahoo.com',
|
||||
}
|
||||
data['aol'] = {
|
||||
'name': 'aol',
|
||||
'display_name': 'AOL',
|
||||
'type': 'openid-username',
|
||||
'extra_token_name': _('AOL screen name'),
|
||||
'icon_media_path': '/jquery-openid/images/aol.gif',
|
||||
'openid_endpoint': 'http://openid.aol.com/%(username)s'
|
||||
}
|
||||
data['openid'] = {
|
||||
'name': 'openid',
|
||||
'display_name': 'OpenID',
|
||||
'type': 'openid-generic',
|
||||
'extra_token_name': _('OpenID url'),
|
||||
'icon_media_path': '/jquery-openid/images/openid.gif',
|
||||
'openid_endpoint': None,
|
||||
}
|
||||
return filter_enabled_providers(data)
|
||||
get_enabled_major_login_providers.is_major = True
|
||||
get_enabled_major_login_providers = add_custom_provider(get_enabled_major_login_providers)
|
||||
|
||||
def get_enabled_minor_login_providers():
|
||||
"""same as get_enabled_major_login_providers
|
||||
but those that are to be displayed with small buttons
|
||||
|
||||
disabled providers are excluded
|
||||
|
||||
structure of dictionary values is the same as in get_enabled_major_login_providers
|
||||
"""
|
||||
data = SortedDict()
|
||||
#data['myopenid'] = {
|
||||
# 'name': 'myopenid',
|
||||
# 'display_name': 'MyOpenid',
|
||||
# 'type': 'openid-username',
|
||||
# 'extra_token_name': _('MyOpenid user name'),
|
||||
# 'icon_media_path': '/jquery-openid/images/myopenid-2.png',
|
||||
# 'openid_endpoint': 'http://%(username)s.myopenid.com'
|
||||
#}
|
||||
data['flickr'] = {
|
||||
'name': 'flickr',
|
||||
'display_name': 'Flickr',
|
||||
'type': 'openid-username',
|
||||
'extra_token_name': _('Flickr user name'),
|
||||
'icon_media_path': '/jquery-openid/images/flickr.png',
|
||||
'openid_endpoint': 'http://flickr.com/%(username)s/'
|
||||
}
|
||||
data['technorati'] = {
|
||||
'name': 'technorati',
|
||||
'display_name': 'Technorati',
|
||||
'type': 'openid-username',
|
||||
'extra_token_name': _('Technorati user name'),
|
||||
'icon_media_path': '/jquery-openid/images/technorati-1.png',
|
||||
'openid_endpoint': 'http://technorati.com/people/technorati/%(username)s/'
|
||||
}
|
||||
data['wordpress'] = {
|
||||
'name': 'wordpress',
|
||||
'display_name': 'WordPress',
|
||||
'type': 'openid-username',
|
||||
'extra_token_name': _('WordPress blog name'),
|
||||
'icon_media_path': '/jquery-openid/images/wordpress.png',
|
||||
'openid_endpoint': 'http://%(username)s.wordpress.com'
|
||||
}
|
||||
data['blogger'] = {
|
||||
'name': 'blogger',
|
||||
'display_name': 'Blogger',
|
||||
'type': 'openid-username',
|
||||
'extra_token_name': _('Blogger blog name'),
|
||||
'icon_media_path': '/jquery-openid/images/blogger-1.png',
|
||||
'openid_endpoint': 'http://%(username)s.blogspot.com'
|
||||
}
|
||||
data['livejournal'] = {
|
||||
'name': 'livejournal',
|
||||
'display_name': 'LiveJournal',
|
||||
'type': 'openid-username',
|
||||
'extra_token_name': _('LiveJournal blog name'),
|
||||
'icon_media_path': '/jquery-openid/images/livejournal-1.png',
|
||||
'openid_endpoint': 'http://%(username)s.livejournal.com'
|
||||
}
|
||||
data['claimid'] = {
|
||||
'name': 'claimid',
|
||||
'display_name': 'ClaimID',
|
||||
'type': 'openid-username',
|
||||
'extra_token_name': _('ClaimID user name'),
|
||||
'icon_media_path': '/jquery-openid/images/claimid-0.png',
|
||||
'openid_endpoint': 'http://claimid.com/%(username)s/'
|
||||
}
|
||||
data['vidoop'] = {
|
||||
'name': 'vidoop',
|
||||
'display_name': 'Vidoop',
|
||||
'type': 'openid-username',
|
||||
'extra_token_name': _('Vidoop user name'),
|
||||
'icon_media_path': '/jquery-openid/images/vidoop.png',
|
||||
'openid_endpoint': 'http://%(username)s.myvidoop.com/'
|
||||
}
|
||||
data['verisign'] = {
|
||||
'name': 'verisign',
|
||||
'display_name': 'Verisign',
|
||||
'type': 'openid-username',
|
||||
'extra_token_name': _('Verisign user name'),
|
||||
'icon_media_path': '/jquery-openid/images/verisign-2.png',
|
||||
'openid_endpoint': 'http://%(username)s.pip.verisignlabs.com/'
|
||||
}
|
||||
return filter_enabled_providers(data)
|
||||
get_enabled_minor_login_providers.is_major = False
|
||||
get_enabled_minor_login_providers = add_custom_provider(get_enabled_minor_login_providers)
|
||||
|
||||
def have_enabled_federated_login_methods():
|
||||
providers = get_enabled_major_login_providers()
|
||||
providers.update(get_enabled_minor_login_providers())
|
||||
provider_types = [provider['type'] for provider in providers.values()]
|
||||
for provider_type in provider_types:
|
||||
if provider_type.startswith('openid') or provider_type == 'oauth':
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_enabled_login_providers():
|
||||
"""return all login providers in one sorted dict
|
||||
"""
|
||||
data = get_enabled_major_login_providers()
|
||||
data.update(get_enabled_minor_login_providers())
|
||||
return data
|
||||
|
||||
def set_login_provider_tooltips(provider_dict, active_provider_names = None):
|
||||
"""adds appropriate tooltip_text field to each provider
|
||||
record, if second argument is None, then tooltip is of type
|
||||
signin with ..., otherwise it's more elaborate -
|
||||
depending on the type of provider and whether or not it's one of
|
||||
currently used
|
||||
"""
|
||||
for provider in provider_dict.values():
|
||||
if active_provider_names:
|
||||
if provider['name'] in active_provider_names:
|
||||
if provider['type'] == 'password':
|
||||
tooltip = _('Change your %(provider)s password') % \
|
||||
{'provider': provider['display_name']}
|
||||
else:
|
||||
tooltip = _(
|
||||
'Click to see if your %(provider)s '
|
||||
'signin still works for %(site_name)s'
|
||||
) % {
|
||||
'provider': provider['display_name'],
|
||||
'site_name': askbot_settings.APP_SHORT_NAME
|
||||
}
|
||||
else:
|
||||
if provider['type'] == 'password':
|
||||
tooltip = _(
|
||||
'Create password for %(provider)s'
|
||||
) % {'provider': provider['display_name']}
|
||||
else:
|
||||
tooltip = _(
|
||||
'Connect your %(provider)s account '
|
||||
'to %(site_name)s'
|
||||
) % {
|
||||
'provider': provider['display_name'],
|
||||
'site_name': askbot_settings.APP_SHORT_NAME
|
||||
}
|
||||
else:
|
||||
if provider['type'] == 'password':
|
||||
tooltip = _(
|
||||
'Signin with %(provider)s user name and password'
|
||||
) % {
|
||||
'provider': provider['display_name'],
|
||||
'site_name': askbot_settings.APP_SHORT_NAME
|
||||
}
|
||||
else:
|
||||
tooltip = _(
|
||||
'Sign in with your %(provider)s account'
|
||||
) % {'provider': provider['display_name']}
|
||||
provider['tooltip_text'] = tooltip
|
||||
|
||||
|
||||
def get_oauth_parameters(provider_name):
|
||||
"""retrieves OAuth protocol parameters
|
||||
from hardcoded settings and adds some
|
||||
from the livesettings
|
||||
|
||||
because this function uses livesettings
|
||||
it should not be called at compile time
|
||||
otherwise there may be strange errors
|
||||
"""
|
||||
providers = get_enabled_login_providers()
|
||||
data = providers[provider_name]
|
||||
if data['type'] != 'oauth':
|
||||
raise ValueError('oauth provider expected, %s found' % data['type'])
|
||||
|
||||
if provider_name == 'twitter':
|
||||
consumer_key = askbot_settings.TWITTER_KEY
|
||||
consumer_secret = askbot_settings.TWITTER_SECRET
|
||||
elif provider_name == 'linkedin':
|
||||
consumer_key = askbot_settings.LINKEDIN_KEY
|
||||
consumer_secret = askbot_settings.LINKEDIN_SECRET
|
||||
elif provider_name == 'identi.ca':
|
||||
consumer_key = askbot_settings.IDENTICA_KEY
|
||||
consumer_secret = askbot_settings.IDENTICA_SECRET
|
||||
elif provider_name == 'facebook':
|
||||
consumer_key = askbot_settings.FACEBOOK_KEY
|
||||
consumer_secret = askbot_settings.FACEBOOK_SECRET
|
||||
else:
|
||||
raise ValueError('unexpected oauth provider %s' % provider_name)
|
||||
|
||||
data['consumer_key'] = consumer_key
|
||||
data['consumer_secret'] = consumer_secret
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class OAuthError(Exception):
|
||||
"""Error raised by the OAuthConnection class
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class OAuthConnection(object):
|
||||
"""a simple class wrapping oauth2 library
|
||||
"""
|
||||
|
||||
def __init__(self, provider_name, callback_url = None):
|
||||
"""initializes oauth connection
|
||||
"""
|
||||
self.provider_name = provider_name
|
||||
self.parameters = get_oauth_parameters(provider_name)
|
||||
self.callback_url = callback_url
|
||||
self.consumer = oauth.Consumer(
|
||||
self.parameters['consumer_key'],
|
||||
self.parameters['consumer_secret'],
|
||||
)
|
||||
|
||||
def start(self, callback_url = None):
|
||||
"""starts the OAuth protocol communication and
|
||||
saves request token as :attr:`request_token`"""
|
||||
|
||||
if callback_url is None:
|
||||
callback_url = self.callback_url
|
||||
|
||||
client = oauth.Client(self.consumer)
|
||||
request_url = self.parameters['request_token_url']
|
||||
|
||||
if callback_url:
|
||||
callback_url = '%s%s' % (askbot_settings.APP_URL, callback_url)
|
||||
request_body = urllib.urlencode(dict(oauth_callback=callback_url))
|
||||
|
||||
self.request_token = self.send_request(
|
||||
client = client,
|
||||
url = request_url,
|
||||
method = 'POST',
|
||||
body = request_body
|
||||
)
|
||||
else:
|
||||
self.request_token = self.send_request(
|
||||
client,
|
||||
request_url,
|
||||
'GET'
|
||||
)
|
||||
|
||||
def send_request(self, client=None, url=None, method='GET', **kwargs):
|
||||
|
||||
response, content = client.request(url, method, **kwargs)
|
||||
if response['status'] == '200':
|
||||
return dict(cgi.parse_qsl(content))
|
||||
else:
|
||||
raise OAuthError('response is %s' % response)
|
||||
|
||||
def get_token(self):
|
||||
return self.request_token
|
||||
|
||||
def get_user_id(self, oauth_token = None, oauth_verifier = None):
|
||||
"""Returns user ID within the OAuth provider system,
|
||||
based on ``oauth_token`` and ``oauth_verifier``
|
||||
"""
|
||||
|
||||
token = oauth.Token(
|
||||
oauth_token['oauth_token'],
|
||||
oauth_token['oauth_token_secret']
|
||||
)
|
||||
token.set_verifier(oauth_verifier)
|
||||
client = oauth.Client(self.consumer, token = token)
|
||||
url = self.parameters['access_token_url']
|
||||
#there must be some provider-specific post-processing
|
||||
data = self.send_request(client = client, url=url, method='GET')
|
||||
data['consumer_key'] = self.parameters['consumer_key']
|
||||
data['consumer_secret'] = self.parameters['consumer_secret']
|
||||
return self.parameters['get_user_id_function'](data)
|
||||
|
||||
def get_auth_url(self, login_only = False):
|
||||
"""returns OAuth redirect url.
|
||||
if ``login_only`` is True, authentication
|
||||
endpoint will be used, if available, otherwise authorization
|
||||
url (potentially granting full access to the server) will
|
||||
be used.
|
||||
|
||||
Typically, authentication-only endpoint simplifies the
|
||||
signin process, but does not allow advanced access to the
|
||||
content on the OAuth-enabled server
|
||||
"""
|
||||
|
||||
endpoint_url = self.parameters.get('authorize_url', None)
|
||||
if login_only == True:
|
||||
endpoint_url = self.parameters.get(
|
||||
'authenticate_url',
|
||||
endpoint_url
|
||||
)
|
||||
if endpoint_url is None:
|
||||
raise ImproperlyConfigured('oauth parameters are incorrect')
|
||||
|
||||
auth_url = '%s?oauth_token=%s' % \
|
||||
(
|
||||
endpoint_url,
|
||||
self.request_token['oauth_token'],
|
||||
)
|
||||
|
||||
return auth_url
|
||||
|
||||
def get_oauth2_starter_url(provider_name, csrf_token):
|
||||
"""returns redirect url for the oauth2 protocol for a given provider"""
|
||||
from sanction.client import Client
|
||||
|
||||
providers = get_enabled_login_providers()
|
||||
params = providers[provider_name]
|
||||
client_id = getattr(askbot_settings, provider_name.upper() + '_KEY')
|
||||
redirect_uri = askbot_settings.APP_URL + reverse('user_complete_oauth2_signin')
|
||||
client = Client(
|
||||
auth_endpoint=params['auth_endpoint'],
|
||||
client_id=client_id,
|
||||
redirect_uri=redirect_uri
|
||||
)
|
||||
return client.auth_uri(state=csrf_token)
|
||||
|
||||
|
||||
def ldap_check_password(username, password):
|
||||
import ldap
|
||||
try:
|
||||
ldap_session = ldap.initialize(askbot_settings.LDAP_URL)
|
||||
ldap_session.simple_bind_s(username, password)
|
||||
ldap_session.unbind_s()
|
||||
return True
|
||||
except ldap.LDAPError, e:
|
||||
logging.critical(unicode(e))
|
||||
return False
|
133
roles/ask/tasks/main.yml
Normal file
133
roles/ask/tasks/main.yml
Normal file
|
@ -0,0 +1,133 @@
|
|||
---
|
||||
#
|
||||
# Setup askbot for ask.fedoraproject.org site.
|
||||
#
|
||||
- name: install needed packages
|
||||
yum: pkg={{ item }} state=installed
|
||||
with_items:
|
||||
- askbot
|
||||
- python-memcached
|
||||
- python-askbot-fedmsg
|
||||
- python-psycopg2
|
||||
- python-django-post_office
|
||||
tags:
|
||||
- packages
|
||||
|
||||
- name: install askbot settings.py template
|
||||
template: >
|
||||
src={{ item }} dest="/etc/askbot/sites/ask/config/settings.py"
|
||||
owner=fedmsg group=fedmsg mode=0600
|
||||
with_items:
|
||||
- settings.py
|
||||
tags:
|
||||
- config
|
||||
|
||||
- name: Install askbot.conf httpd config
|
||||
copy: >
|
||||
src=askbot.conf dest=/etc/httpd/conf.d/askbot.conf
|
||||
owner=root group=root mode=0644
|
||||
tags:
|
||||
- files
|
||||
notify:
|
||||
- restart httpd
|
||||
|
||||
#
|
||||
# we add this wsgi to handle ssl issues in stg
|
||||
#
|
||||
- name: Install askbot.wsgi httpd config
|
||||
copy: >
|
||||
src=askbot.wsgi dest=/usr/sbin/askbot.wsgi
|
||||
owner=root group=root mode=0755
|
||||
tags:
|
||||
- files
|
||||
notify:
|
||||
- restart httpd
|
||||
|
||||
- name: Install askbot cron jobs
|
||||
copy: >
|
||||
src={{ item }} dest=/etc/cron.d/{{ item }}
|
||||
owner=root group=root mode=0644
|
||||
with_items:
|
||||
- cron-ask-send-reminders
|
||||
- cron-post-office-send-mail
|
||||
tags:
|
||||
- files
|
||||
|
||||
- name: setup default skin link needed for askbot
|
||||
file: state=link src=/usr/lib/python2.6/site-packages/askbot/skins/default dest=/usr/lib/python2.6/site-packages/askbot/static/default
|
||||
|
||||
- name: setup admin skin link needed for askbot
|
||||
file: state=link src=/usr/lib/python2.6/site-packages/askbot/skins/admin dest=/usr/lib/python2.6/site-packages/askbot/static/admin
|
||||
|
||||
#
|
||||
# ?
|
||||
#
|
||||
- name: HOTFIX: askbot backends.py
|
||||
copy: >
|
||||
src=backends.py dest=/usr/lib/python2.6/site-packages/post_office/backends.py
|
||||
owner=root group=root mode=0644
|
||||
tags:
|
||||
- files
|
||||
notify:
|
||||
- restart httpd
|
||||
|
||||
#
|
||||
# Adds fedora openid login button
|
||||
#
|
||||
- name: HOTFIX: askbot login_providers.py
|
||||
copy: >
|
||||
src=login_providers.py dest=/usr/lib/python2.6/site-packages/askbot/conf/login_providers.py
|
||||
owner=root group=root mode=0644
|
||||
tags:
|
||||
- files
|
||||
notify:
|
||||
- restart httpd
|
||||
|
||||
#
|
||||
# Adds fedora openid login button
|
||||
#
|
||||
- name: HOTFIX: askbot util.py
|
||||
copy: >
|
||||
src=util.py dest=/usr/lib/python2.6/site-packages/askbot/deps/django_authopenid/util.py
|
||||
owner=root group=root mode=0644
|
||||
tags:
|
||||
- files
|
||||
notify:
|
||||
- restart httpd
|
||||
|
||||
#
|
||||
# fedora openid icon for login screen
|
||||
#
|
||||
- name: HOTFIX: askbot fedora-openid.png
|
||||
copy: >
|
||||
src=fedora-openid.png dest=/usr/lib/python2.6/site-packages/askbot/media/jquery-openid/images/fedora-openid.png
|
||||
owner=root group=root mode=0644
|
||||
tags:
|
||||
- files
|
||||
notify:
|
||||
- restart httpd
|
||||
|
||||
#
|
||||
# fedora openid icon for login screen
|
||||
#
|
||||
- name: HOTFIX: askbot fedora-openid.png
|
||||
copy: >
|
||||
src=fedora-openid.png dest=/usr/lib/python2.6/site-packages/askbot/static/default/media/jquery-openid/images/fedora-openid.png
|
||||
owner=root group=root mode=0644
|
||||
tags:
|
||||
- files
|
||||
notify:
|
||||
- restart httpd
|
||||
|
||||
#
|
||||
# fixes login with facebook.
|
||||
#
|
||||
|
||||
- name: HOTFIX: askbot sanction-client.py
|
||||
copy: >
|
||||
src=sanction-client.py dest=/usr/lib/python2.6/site-packages/sanction/client.py
|
||||
owner=root group=root mode=0644
|
||||
tags:
|
||||
- files
|
||||
notify:
|
||||
- restart httpd
|
344
roles/ask/templates/settings.py
Normal file
344
roles/ask/templates/settings.py
Normal file
|
@ -0,0 +1,344 @@
|
|||
## Django settings for ASKBOT enabled project.
|
||||
import os.path
|
||||
import logging
|
||||
import sys
|
||||
import askbot
|
||||
import site
|
||||
|
||||
#this line is added so that we can import pre-packaged askbot dependencies
|
||||
ASKBOT_ROOT = os.path.abspath(os.path.dirname(askbot.__file__))
|
||||
site.addsitedir(os.path.join(ASKBOT_ROOT, 'deps'))
|
||||
|
||||
DEBUG = False#set to True to enable debugging
|
||||
TEMPLATE_DEBUG = False#keep false when debugging jinja2 templates
|
||||
INTERNAL_IPS = ('127.0.0.1',)
|
||||
ALLOWED_HOSTS = ['*',]#change this for better security on your site
|
||||
|
||||
ADMINS = (
|
||||
('AskFedora Sysadmins', 'sysadmin-ask-members@fedoraproject.org'),
|
||||
)
|
||||
|
||||
MANAGERS = ADMINS
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||
'NAME': 'askfedora',
|
||||
'USER': 'askfedora', # Not used with sqlite3.
|
||||
'PASSWORD': '<%= askbotDBPassword %>', # Not used with sqlite3.
|
||||
{% if env == "staging" %}
|
||||
'HOST' : 'db01',
|
||||
{% else %}
|
||||
'HOST' : 'db-ask',
|
||||
{% end %}
|
||||
# Set to empty string for localhost. Not used with sqlite3.
|
||||
'PORT': '5432', # Set to empty string for default. Not used with sqlite3.
|
||||
'TEST_CHARSET': 'utf8', # Setting the character set and collation to utf-8
|
||||
'TEST_COLLATION': 'utf8_general_ci', # is necessary for MySQL tests to work properly.
|
||||
}
|
||||
}
|
||||
|
||||
#outgoing mail server settings
|
||||
SERVER_EMAIL = 'nobody@fedoraproject.org'
|
||||
DEFAULT_FROM_EMAIL = 'nobody@fedoraproject.org'
|
||||
EMAIL_HOST_USER = ''
|
||||
EMAIL_HOST_PASSWORD = ''
|
||||
EMAIL_SUBJECT_PREFIX = ''
|
||||
EMAIL_HOST='bastion'
|
||||
EMAIL_PORT='25'
|
||||
EMAIL_USE_TLS=False
|
||||
EMAIL_BACKEND = 'post_office.EmailBackend'
|
||||
|
||||
|
||||
#incoming mail settings
|
||||
#after filling out these settings - please
|
||||
#go to the site's live settings and enable the feature
|
||||
#"Email settings" -> "allow asking by email"
|
||||
#
|
||||
# WARNING: command post_emailed_questions DELETES all
|
||||
# emails from the mailbox each time
|
||||
# do not use your personal mail box here!!!
|
||||
#
|
||||
IMAP_HOST = ''
|
||||
IMAP_HOST_USER = ''
|
||||
IMAP_HOST_PASSWORD = ''
|
||||
IMAP_PORT = ''
|
||||
IMAP_USE_TLS = False
|
||||
|
||||
# Local time zone for this installation. Choices can be found here:
|
||||
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
|
||||
# although not all choices may be available on all operating systems.
|
||||
# On Unix systems, a value of None will cause Django to use the same
|
||||
# timezone as the operating system.
|
||||
# If running in a Windows environment this must be set to the same as your
|
||||
# system time zone.
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
# If you set this to False, Django will make some optimizations so as not
|
||||
# to load the internationalization machinery.
|
||||
USE_I18N = True
|
||||
LANGUAGE_CODE = 'en'
|
||||
|
||||
# Absolute path to the directory that holds uploaded media
|
||||
# Example: "/home/media/media.lawrence.com/"
|
||||
MEDIA_ROOT = os.path.join(os.path.dirname(__file__), 'askbot', 'upfiles')
|
||||
MEDIA_URL = '/upfiles/'
|
||||
STATIC_URL = '/m/'#this must be different from MEDIA_URL
|
||||
|
||||
PROJECT_ROOT = os.path.dirname(__file__)
|
||||
#STATIC_ROOT = os.path.join(PROJECT_ROOT, 'static')
|
||||
STATIC_ROOT = '/var/www/html/askbot/static'
|
||||
|
||||
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
|
||||
# trailing slash.
|
||||
# Examples: "http://foo.com/media/", "/media/".
|
||||
ADMIN_MEDIA_PREFIX = STATIC_URL + 'admin/'
|
||||
|
||||
# Make up some unique string, and don't share it with anybody.
|
||||
SECRET_KEY = '<%= askbotSecretKeyPassword %>'
|
||||
|
||||
# List of callables that know how to import templates from various sources.
|
||||
TEMPLATE_LOADERS = (
|
||||
'askbot.skins.loaders.Loader',
|
||||
'django.template.loaders.app_directories.Loader',
|
||||
'django.template.loaders.filesystem.Loader',
|
||||
#'django.template.loaders.eggs.load_template_source',
|
||||
)
|
||||
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
#'django.middleware.gzip.GZipMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
## Enable the following middleware if you want to enable
|
||||
## language selection in the site settings.
|
||||
#'askbot.middleware.locale.LocaleMiddleware',
|
||||
#'django.middleware.cache.UpdateCacheMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
#'django.middleware.cache.FetchFromCacheMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
#'django.middleware.sqlprint.SqlPrintingMiddleware',
|
||||
|
||||
#below is askbot stuff for this tuple
|
||||
'askbot.middleware.anon_user.ConnectToSessionMessagesMiddleware',
|
||||
'askbot.middleware.forum_mode.ForumModeMiddleware',
|
||||
'askbot.middleware.cancel.CancelActionMiddleware',
|
||||
'django.middleware.transaction.TransactionMiddleware',
|
||||
#'debug_toolbar.middleware.DebugToolbarMiddleware',
|
||||
'askbot.middleware.view_log.ViewLogMiddleware',
|
||||
'askbot.middleware.spaceless.SpacelessMiddleware',
|
||||
)
|
||||
|
||||
|
||||
ROOT_URLCONF = os.path.basename(os.path.dirname(__file__)) + '.urls'
|
||||
|
||||
|
||||
#UPLOAD SETTINGS
|
||||
FILE_UPLOAD_TEMP_DIR = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
'tmp'
|
||||
).replace('\\','/')
|
||||
|
||||
FILE_UPLOAD_HANDLERS = (
|
||||
'django.core.files.uploadhandler.MemoryFileUploadHandler',
|
||||
'django.core.files.uploadhandler.TemporaryFileUploadHandler',
|
||||
)
|
||||
ASKBOT_ALLOWED_UPLOAD_FILE_TYPES = ('.jpg', '.jpeg', '.gif', '.bmp', '.png', '.tiff')
|
||||
ASKBOT_MAX_UPLOAD_FILE_SIZE = 1024 * 1024 #result in bytes
|
||||
DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
|
||||
|
||||
|
||||
#TEMPLATE_DIRS = (,) #template have no effect in askbot, use the variable below
|
||||
#ASKBOT_EXTRA_SKINS_DIR = #path to your private skin collection
|
||||
#take a look here http://askbot.org/en/question/207/
|
||||
|
||||
TEMPLATE_CONTEXT_PROCESSORS = (
|
||||
'django.core.context_processors.request',
|
||||
'askbot.context.application_settings',
|
||||
#'django.core.context_processors.i18n',
|
||||
'askbot.user_messages.context_processors.user_messages',#must be before auth
|
||||
'django.contrib.auth.context_processors.auth', #this is required for the admin app
|
||||
'django.core.context_processors.csrf', #necessary for csrf protection
|
||||
)
|
||||
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'longerusername',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.staticfiles',
|
||||
|
||||
#all of these are needed for the askbot
|
||||
'django.contrib.admin',
|
||||
'django.contrib.humanize',
|
||||
'django.contrib.sitemaps',
|
||||
'django.contrib.messages',
|
||||
#'debug_toolbar',
|
||||
#Optional, to enable haystack search
|
||||
#'haystack',
|
||||
'compressor',
|
||||
'askbot',
|
||||
'askbot.deps.django_authopenid',
|
||||
#'askbot.importers.stackexchange', #se loader
|
||||
'south',
|
||||
'askbot.deps.livesettings',
|
||||
'keyedcache',
|
||||
'robots',
|
||||
'django_countries',
|
||||
#'djcelery',
|
||||
'djkombu',
|
||||
'followit',
|
||||
'tinymce',
|
||||
'group_messaging',
|
||||
#'avatar',#experimental use git clone git://github.com/ericflo/django-avatar.git$
|
||||
)
|
||||
|
||||
|
||||
#setup memcached for production use!
|
||||
#see http://docs.djangoproject.com/en/1.1/topics/cache/ for details
|
||||
{% if env == "staging" %}
|
||||
CACHE_BACKEND = 'locmem://'
|
||||
{% else %}
|
||||
CACHE_BACKEND='memcached://memcached04:11211/'
|
||||
{% end %}
|
||||
#needed for django-keyedcache
|
||||
CACHE_TIMEOUT = 6000
|
||||
#sets a special timeout for livesettings if you want to make them different
|
||||
LIVESETTINGS_CACHE_TIMEOUT = CACHE_TIMEOUT
|
||||
CACHE_PREFIX = 'askbot' #make this unique
|
||||
CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True
|
||||
#If you use memcache you may want to uncomment the following line to enable memcached based sessions
|
||||
#SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
|
||||
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
'askbot.deps.django_authopenid.backends.AuthBackend',
|
||||
)
|
||||
|
||||
#logging settings
|
||||
LOG_FILENAME = 'askfedora.log'
|
||||
logging.basicConfig(
|
||||
filename=os.path.join('/var', 'log', 'askbot', LOG_FILENAME),
|
||||
level=logging.CRITICAL,
|
||||
format='%(pathname)s TIME: %(asctime)s MSG: %(filename)s:%(funcName)s:%(lineno)d %(message)s',
|
||||
)
|
||||
|
||||
###########################
|
||||
#
|
||||
# this will allow running your forum with url like http://site.com/forum
|
||||
#
|
||||
# ASKBOT_URL = 'forum/'
|
||||
#
|
||||
ASKBOT_URL = '' #no leading slash, default = '' empty string
|
||||
ASKBOT_TRANSLATE_URL = True #translate specific URLs
|
||||
_ = lambda v:v #fake translation function for the login url
|
||||
LOGIN_URL = '/%s%s%s' % (ASKBOT_URL,_('account/'),_('signin/'))
|
||||
LOGIN_REDIRECT_URL = ASKBOT_URL #adjust, if needed
|
||||
#note - it is important that upload dir url is NOT translated!!!
|
||||
#also, this url must not have the leading slash
|
||||
ALLOW_UNICODE_SLUGS = False
|
||||
ASKBOT_USE_STACKEXCHANGE_URLS = False #mimic url scheme of stackexchange
|
||||
|
||||
#Celery Settings
|
||||
BROKER_TRANSPORT = "djkombu.transport.DatabaseTransport"
|
||||
CELERY_ALWAYS_EAGER = True
|
||||
|
||||
|
||||
|
||||
{% if environment == "staging" %}
|
||||
DOMAIN_NAME = 'ask.stg.fedoraproject.org'
|
||||
{% else %}
|
||||
DOMAIN_NAME = 'ask.fedoraproject.org'
|
||||
{% end %}
|
||||
#https://docs.djangoproject.com/en/1.3/ref/contrib/csrf/
|
||||
CSRF_COOKIE_DOMAIN = DOMAIN_NAME
|
||||
|
||||
#STATIC_ROOT = os.path.join(PROJECT_ROOT, "static")
|
||||
STATICFILES_DIRS = (
|
||||
('default/media', os.path.join(ASKBOT_ROOT, 'media')),
|
||||
)
|
||||
STATICFILES_FINDERS = (
|
||||
'django.contrib.staticfiles.finders.FileSystemFinder',
|
||||
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
|
||||
'compressor.finders.CompressorFinder',
|
||||
)
|
||||
|
||||
# Since Django 1.2.7 we need to add this or it will not forward openid requests correctly.
|
||||
USE_X_FORWARDED_HOST = True
|
||||
|
||||
# Since askbot 0.7.32 we need to have a redirect url after login
|
||||
LOGIN_REDIRECT_URL = ASKBOT_URL
|
||||
|
||||
RECAPTCHA_USE_SSL = True
|
||||
|
||||
#HAYSTACK_SETTINGS
|
||||
ENABLE_HAYSTACK_SEARCH = False
|
||||
#Uncomment for multilingual setup:
|
||||
#HAYSTACK_ROUTERS = ['askbot.search.haystack.routers.LanguageRouter',]
|
||||
|
||||
#Uncomment if you use haystack
|
||||
#More info in http://django-haystack.readthedocs.org/en/latest/settings.html
|
||||
#HAYSTACK_CONNECTIONS = {
|
||||
# 'default': {
|
||||
# 'ENGINE': 'haystack.backends.simple_backend.SimpleEngine',
|
||||
# }
|
||||
#}
|
||||
|
||||
|
||||
TINYMCE_COMPRESSOR = True
|
||||
TINYMCE_SPELLCHECKER = False
|
||||
TINYMCE_JS_ROOT = os.path.join(STATIC_ROOT, 'default/media/js/tinymce/')
|
||||
#TINYMCE_JS_URL = STATIC_URL + 'default/media/js/tinymce/tiny_mce.js'
|
||||
TINYMCE_DEFAULT_CONFIG = {
|
||||
'plugins': 'askbot_imageuploader,askbot_attachment',
|
||||
'convert_urls': False,
|
||||
'theme': 'advanced',
|
||||
'content_css': STATIC_URL + 'default/media/style/tinymce/content.css',
|
||||
'force_br_newlines': True,
|
||||
'force_p_newlines': False,
|
||||
'forced_root_block': '',
|
||||
'mode' : 'textareas',
|
||||
'oninit': "TinyMCE.onInitHook",
|
||||
'plugins': 'askbot_imageuploader,askbot_attachment',
|
||||
'theme_advanced_toolbar_location' : 'top',
|
||||
'theme_advanced_toolbar_align': 'left',
|
||||
'theme_advanced_buttons1': 'bold,italic,underline,|,bullist,numlist,|,undo,redo,|,link,unlink,askbot_imageuploader,askbot_attachment',
|
||||
'theme_advanced_buttons2': '',
|
||||
'theme_advanced_buttons3' : '',
|
||||
'theme_advanced_path': False,
|
||||
'theme_advanced_resizing': True,
|
||||
'theme_advanced_resize_horizontal': False,
|
||||
'theme_advanced_statusbar_location': 'bottom',
|
||||
'width': '730',
|
||||
'height': '250'
|
||||
}
|
||||
|
||||
#delayed notifications, time in seconds, 15 mins by default
|
||||
NOTIFICATION_DELAY_TIME = 60 * 15
|
||||
|
||||
GROUP_MESSAGING = {
|
||||
'BASE_URL_GETTER_FUNCTION': 'askbot.models.user_get_profile_url',
|
||||
'BASE_URL_PARAMS': {'section': 'messages', 'sort': 'inbox'}
|
||||
}
|
||||
|
||||
ASKBOT_MULTILINGUAL = False
|
||||
|
||||
ASKBOT_CSS_DEVEL = False
|
||||
if 'ASKBOT_CSS_DEVEL' in locals() and ASKBOT_CSS_DEVEL == True:
|
||||
COMPRESS_PRECOMPILERS = (
|
||||
('text/less', 'lessc {infile} {outfile}'),
|
||||
)
|
||||
|
||||
COMPRESS_JS_FILTERS = []
|
||||
COMPRESS_PARSER = 'compressor.parser.HtmlParser'
|
||||
JINJA2_EXTENSIONS = ('compressor.contrib.jinja2ext.CompressorExtension',)
|
||||
|
||||
# Use syncdb for tests instead of South migrations. Without this, some tests
|
||||
# fail spuriously in MySQL.
|
||||
SOUTH_TESTS_MIGRATE = False
|
||||
|
||||
VERIFIER_EXPIRE_DAYS = 3
|
Loading…
Add table
Add a link
Reference in a new issue