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