service = models.URLField(verify_exists=False)
ticket = models.CharField(max_length=256)
created = models.DateTimeField(auto_now=True)
-
+
def __unicode__(self):
return "%s (%s) - %s" % (self.user.username, self.service, self.created)
-
+
class LoginTicket(models.Model):
ticket = models.CharField(max_length=32)
created = models.DateTimeField(auto_now=True)
-
+
def __unicode__(self):
return "%s - %s" % (self.ticket, self.created)
if settings.CAS_CUSTOM_ATTRIBUTES_CALLBACK:
callback = get_callable(settings.CAS_CUSTOM_ATTRIBUTES_CALLBACK)
attrs = callback(user)
-
+
response = ElementRoot(CAS + 'serviceResponse')
auth_success = etree.SubElement(response, CAS + 'authenticationSuccess')
username = etree.SubElement(auth_success, CAS + 'user')
from django.contrib.auth.models import User
from django.contrib.auth import authenticate
from django.contrib.auth import login as auth_login, logout as auth_logout
+from django.utils.translation import ugettext_lazy as _
from cas_provider.forms import LoginForm
from cas_provider.models import ServiceTicket, LoginTicket, auth_success_response
from urlparse import parse_qs as url_parse_qs
except ImportError:
from cgi import parse_qs as url_parse_qs
-
import logging
-logger = logging.getLogger("fnp.cas.provider")
+logger = logging.getLogger("cas.provider")
__all__ = ['login', 'validate', 'service_validate', 'logout']
-def _add_query_param(url, param, value):
+def _add_query_param(url, param, value):
parsed = urlparse.urlparse(url)
query = url_parse_qs(parsed.query)
query[param] = [unicode(value, 'utf-8')]
return parsed.geturl()
-def login(request, template_name = 'cas/login.html', success_redirect = '/accounts/'):
+def login(request, template_name='cas/login.html', success_redirect='/accounts/'):
service = request.GET.get('service', None)
-
+
if request.user.is_authenticated():
- logger.info("User %s passed auth, service is %s", request.user, service)
-
if service is not None:
ticket = create_service_ticket(request.user, service)
target = _add_query_param(service, 'ticket', ticket.ticket)
else:
logger.info("Redirecting to default: %s", success_redirect)
return HttpResponseRedirect(success_redirect)
-
+
errors = []
if request.method == 'POST':
username = request.POST.get('username', None)
password = request.POST.get('password', None)
service = request.POST.get('service', None)
lt = request.POST.get('lt', None)
-
+
logger.debug("User %s logging in", username)
- logger.info("Login submit: serivce = %s, Lticket=%s",service, lt)
try:
- login_ticket = LoginTicket.objects.get(ticket = lt)
+ login_ticket = LoginTicket.objects.get(ticket=lt)
except:
- errors.append('Login ticket expired. Please try again.')
+ errors.append(_(u'Login ticket expired. Please try again.'))
else:
login_ticket.delete()
- logger.debug("Auth")
- user = authenticate(username = username, password = password)
+ user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
- logger.debug("AuthLogin")
auth_login(request, user)
- if service is not None:
+ if service is not None:
ticket = create_service_ticket(user, service)
- logger.info("Service=%s, ticket=%s", service, ticket)
target = _add_query_param(service, 'ticket', ticket.ticket)
- logger.info("Redirecting to %s", target)
return HttpResponseRedirect(target)
else:
- logger.info("Redirecting to default: %s", success_redirect)
return HttpResponseRedirect(success_redirect)
else:
- errors.append('This account is disabled.')
+ errors.append(_(u'This account is disabled.'))
else:
- errors.append('Incorrect username and/or password.')
-
- logger.debug("LOGIN GET, service = %s", service)
+ errors.append(_(u'Incorrect username and/or password.'))
+
form = LoginForm(service)
- return render_to_response(template_name, {'form': form, 'errors': errors}, context_instance = RequestContext(request))
+ return render_to_response(template_name, {'form': form, 'errors': errors}, context_instance=RequestContext(request))
def validate(request):
service = request.GET.get('service', None)
ticket_string = request.GET.get('ticket', None)
if service is not None and ticket_string is not None:
try:
- ticket = ServiceTicket.objects.get(ticket = ticket_string)
+ ticket = ServiceTicket.objects.get(ticket=ticket_string)
username = ticket.user.username
ticket.delete()
return HttpResponse("yes\n%s\n" % username)
service = request.GET.get('service', None)
ticket_string = request.GET.get('ticket', None)
if service is None or ticket_string is None:
- return HttpResponse('''<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
+ return HttpResponse(r'''<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
<cas:authenticationFailure code="INVALID_REQUEST">
Not all required parameters were sent.
</cas:authenticationFailure>
- </cas:serviceResponse>''', mimetype = 'text/xml')
+ </cas:serviceResponse>''', mimetype='application/xml')
try:
- ticket = ServiceTicket.objects.get(ticket = ticket_string)
+ ticket = ServiceTicket.objects.get(ticket=ticket_string)
ticket.delete()
- return HttpResponse(auth_success_response(ticket.user), mimetype = 'text/xml')
+ return HttpResponse(auth_success_response(ticket.user), mimetype='text/xml')
except ServiceTicket.DoesNotExist:
- return HttpResponse('''<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
+ return HttpResponse(r'''<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
<cas:authenticationFailure code="INVALID_TICKET">
The provided ticket is invalid.
</cas:authenticationFailure>
- </cas:serviceResponse>''', mimetype = 'text/xml')
+ </cas:serviceResponse>''', mimetype='application/xml')
-def logout(request, template_name = 'cas/logout.html'):
+def logout(request, template_name='cas/logout.html'):
url = request.GET.get('url', None)
auth_logout(request)
- return render_to_response(template_name, {'url': url}, context_instance = RequestContext(request))
+ return render_to_response(template_name, {'url': url}, context_instance=RequestContext(request))
from setuptools import setup, find_packages
-
+
setup(
name='django-cas-provider',
version='0.2',
--- /dev/null
+from django import forms
+from django.contrib.auth.models import User as DjangoUser
+from django.utils.translation import ugettext_lazy as _
+
+class UserBasicForm(forms.ModelForm):
+
+ class Meta:
+ model = DjangoUser
+ fields = ('first_name', 'last_name', 'email',)
+
+
+class UserPasswordForm(forms.Form):
+
+ new_password = forms.CharField(widget=forms.PasswordInput(),
+ label=_("Your new password"))
+ verifier = forms.CharField(widget=forms.PasswordInput(),
+ label=_("Repeated password"))
+
+ def clean(self):
+ if 'verifier' not in self.cleaned_data or 'new_password' not in self.cleaned_data:
+ return self.cleaned_data
+
+ if self.cleaned_data['verifier'] != self.cleaned_data['new_password']:
+ raise forms.ValidationError(_("Passwords do not match!"))
+
+ return self.cleaned_data
--- /dev/null
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-04-18 10:48+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: forms.py:15
+msgid "Your new password"
+msgstr "Twoje nowe hasło"
+
+#: forms.py:17
+msgid "Repeated password"
+msgstr "Powtórzone hasło"
+
+#: forms.py:24
+msgid "Passwords do not match!"
+msgstr "Hasło i powtórzenie się nie zgadzają!"
+
+#: templates/account/profile.html:12
+msgid "Your profile"
+msgstr "Twój profil"
+
+#: templates/account/profile.html:16
+msgid "Change profile"
+msgstr "Zmień profil"
+
+#: templates/account/profile.html:20
+msgid "Password change"
+msgstr "Zmiana hasła"
+
+#: templates/account/profile.html:24
+msgid "Change password"
+msgstr "Zmień hasło"
+
+#: templates/account/profile.html:30
+msgid "Availble services"
+msgstr "Dostępne usługi"
--- /dev/null
+from django.db import models
+
+# Create your models here.
--- /dev/null
+{% extends "base.html" %}
+{% load gravatar i18n %}
+
+{% block content %}
+ <div class="user_avatar">
+ <img src="{% gravatar request.user.email 120 %}">
+ <p><a href="{% url cas_provider.views.logout %}">{% trans "Logout" %}</a></p>
+ </div>
+
+ <div id="details">
+ {% if messages %}
+ <p>{% for message in messages %}<span class="message">{{ message }}</span>{% endfor %}</p>
+ {% endif %}
+
+ <h2>{% trans "Your profile" %}</h2>
+ <form method="post" action="/accounts/change_profile">
+ <table>
+ {{ basic_form.as_table }}
+ <tr><td colspan="2"><button type="submit">{% trans "Change profile" %}</button></td></tr>
+ </table>
+ </form>
+
+ <h2>{% trans "Password change" %}</h2>
+ <form method="post" action="/accounts/change_password">
+ <table>
+ {{ pass_form.as_table }}
+ <tr><td colspan="2"><button type="submit">{% trans "Change password" %}</button></td></tr>
+ </table>
+ </form>
+ </div>
+
+ <div id="services-list">
+ <h2>{% trans "Availble services" %}</h2>
+ <a href="http://redakcja.nowoczesnapolska.org.pl/">
+ <img src="http://www.nowoczesnapolska.org.pl/wordpress/wp-content/uploads/2008/11/wl-logo-white.png">
+ </a>
+ <a href="http://redmine.nowoczesnapolska.org.pl/">
+ <img src="{{ MEDIA_URL }}static/redmine_logo.png">
+ </a>
+ </div>
+{% endblock %}
\ No newline at end of file
--- /dev/null
+from django import template
+from django.template.defaultfilters import stringfilter
+import hashlib
+import urllib
+
+register = template.Library()
+
+DEFAULTS = dict(size=80, rating='g', default='monsterid')
+
+class GravatarNode(template.Node):
+
+ def __init__(self, email, size):
+ self.email = template.Variable(email)
+ self.size = size
+
+ def render(self, context):
+ try:
+ email = self.email.resolve(context)
+ except template.VariableDoesNotExist:
+ return ''
+
+ gravatar_url = "http://www.gravatar.com/avatar/" + hashlib.md5(email).hexdigest() + "?"
+ gravatar_url += urllib.urlencode({'default': 'wavatar', 'size': str(self.size)})
+
+ return gravatar_url
+
+@register.tag
+def gravatar(parser, token):
+ try:
+ _tag_name, email, size = token.split_contents()
+ except ValueError:
+ raise template.TemplateSyntaxError, "%r tag requires two args" % token.contents.split()[0]
+
+ return GravatarNode(email, int(size))
+
+
+
+
+@register.filter(name='md5')
+@stringfilter
+def md5_hash(value):
+ h = hashlib.md5()
+ h.update(value)
+ return h.hexdigest()
--- /dev/null
+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+class SimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.failUnlessEqual(1 + 1, 2)
+
+__test__ = {"doctest": """
+Another way to test that 1 + 1 is equal to 2.
+
+>>> 1 + 1 == 2
+True
+"""}
+
--- /dev/null
+# -*- coding: utf-8 -*-
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('accounts.views',
+ url(r'^$', 'account_profile'),
+ url(r'^change_profile$', 'account_change_basic_profile'),
+ url(r'^change_password$', 'account_change_password'),
+)
--- /dev/null
+# Create your views here.
+from django import http
+from django.utils.translation import ugettext as __
+from django.views.generic.simple import direct_to_template
+from django.views.decorators.http import require_POST
+from django.contrib.auth.decorators import login_required
+from accounts.forms import UserBasicForm, UserPasswordForm
+
+@login_required
+def account_profile(request, basic_form=None, pass_form=None):
+ return direct_to_template(request, "account/profile.html", {
+ "basic_form": basic_form or UserBasicForm(instance=request.user),
+ "pass_form": pass_form or UserPasswordForm(),
+ })
+
+
+@require_POST
+@login_required
+def account_change_basic_profile(request):
+ form = UserBasicForm(request.POST, instance=request.user)
+
+ if form.is_valid():
+ form.save()
+ request.user.message_set.create(message=__("Profile has been changed."))
+ return http.HttpResponseRedirect('/accounts/')
+
+ return account_profile(request, basic_form=form)
+
+@require_POST
+@login_required
+def account_change_password(request):
+ form = UserPasswordForm(request.POST)
+
+ if form.is_valid():
+ request.user.set_password(form.cleaned_data['new_password'])
+ request.user.save()
+
+ request.user.message_set.create(message=__("Password has been changed."))
+ return http.HttpResponseRedirect('/accounts/')
+
+ return account_profile(request, pass_form=form)
from os import path
import sys
-PROJECT_ROOT = path.realpath(path.dirname(__file__))
-sys.path.insert(0, path.abspath(path.join(PROJECT_ROOT, "..", "provider")))
-print sys.path
+PROJECT_ROOT = path.realpath(path.dirname(__file__))
+sys.path = [
+ path.abspath(path.join(PROJECT_ROOT, '..')),
+ path.abspath(path.join(PROJECT_ROOT, '..', '..', "provider")),
+] + sys.path
try:
import settings # Assumed to be in the same directory.
except ImportError:
import traceback
- traceback.print_exc(file =sys.stderr)
+ traceback.print_exc(file=sys.stderr)
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
sys.exit(1)
if __name__ == "__main__":
# Append lib and apps directories to PYTHONPATH
-
-
execute_manager(settings)
--- /dev/null
+body {
+ background-color: #eee;
+}
+
+#content_wrap {
+ background-color: #555;
+ margin: 0px 5%;
+ padding: 5px;
+}
+
+#content {
+ background-color: white;
+ border-radius: 10px;
+ margin: 5px;
+ padding: 0.2em 1em;
+ overflow: hidden;
+}
+
+#details {
+ margin: 1em;
+ float: left;
+ max-width: 75%;
+
+}
+
+#details form table th {
+ text-align: left;
+}
+
+#details form {
+ margin-bottom: 2em;
+}
+
+.user_avatar {
+ float: left;
+ margin: 1em;
+ max-width: 20%;
+ text-align: center;
+}
+
+.user_avatar img {
+ border: 1px solid black;
+}
+
+#services-list {
+ width: 250px;
+ float: right;
+ margin: 1em 2em;
+}
+
+#services-list img {
+ width: 250px;
+ margin: 1em 0em;
+}
+
+img.small_logo {
+ width: 250px;
+}
DEBUG = True
TEMPLATE_DEBUG = DEBUG
-ADMINS = []
+ADMINS = [
+ "lrekucki@gmail.com",
+]
MANAGERS = ADMINS
# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/"
MEDIA_ROOT = PROJECT_ROOT + '/media/'
-STATIC_ROOT = PROJECT_ROOT + '/static/'
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash if there is a path component (optional in other cases).
# Examples: "http://media.lawrence.com", "http://example.com/media/"
MEDIA_URL = '/media/'
-STATIC_URL = '/static/'
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
"django.core.context_processors.auth",
"django.core.context_processors.debug",
"django.core.context_processors.i18n",
+ 'django.core.context_processors.media',
"django.core.context_processors.request",
)
ROOT_URLCONF = 'cas.urls'
TEMPLATE_DIRS = (
- PROJECT_ROOT + '/templates',
+ PROJECT_ROOT + '/templates',
)
INSTALLED_APPS = (
'django.contrib.sites',
'django.contrib.admin',
'django.contrib.admindocs',
-
+
'cas_provider',
+ 'accounts',
)
# django-cas-provider settings
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+ <title>{% block title %}Fundacja Nowoczesna Polska - CAS{% block subtitle %}{% endblock subtitle %}{% endblock title%}</title>
+ <link rel="stylesheet" href="{{ MEDIA_URL }}static/css/main.css"/>
+ {% block extrahead %}
+ {% endblock %}
+ </head>
+ <body>
+ <div id="content_wrap"><div id="content">{% block content %} {% endblock %}</div></div>
+ </body>
+</html>
-{% extends "cas_base.html" %}
+{% extends "base.html" %}
+{% load i18n %}
{% block content %}
+
<form action='.' method='post'>
- <fieldset>
- <legend>Zaloguj się</legend>
- {% if errors %}
- <ul>
- {% for error in errors %}
- <li>{{ error|escape }}</li>
- {% endfor %}
- </ul>
- {% endif %}
- <table style="border: none;">
- {{ form.as_table }}
- </table>
- <p><input type="submit" value="Login"/></p>
- </fieldset>
+ <h1>{% trans "Login" %}</h1>
+
+ {% for error in errors %}
+ <p>{{ error }}</p>
+ {% endfor %}
+
+ <table>
+ {{ form.as_table }}
+ <tr><td><input type="submit" value="{% trans "Login me in" %}"/></td></tr>
+ </table>
+
</form>
+
+ {% include "horizontal_footer.html" %}
{% endblock %}
-
\ No newline at end of file
-{% extends "cas_base.html" %}
-
+{% extends "base.html" %}
+{% load i18n %}
+
{% block title %}
Logged out
{% endblock %}
-
+
{% block content %}
- <h3>Logged out</h3>
-
- <p>You have successfully logged out. To ensure that you are logged out of all services, please close your browser.</p>
- {% if url %}<p><a href="{{ url }}">Click here</a> to return to {{ url }}</p>{% endif %}
+ <h1>{% trans "Logged out" %}</h1>
+
+ <p>{% blocktrans %}You have successfully logged out. To ensure that you are logged out of all services, please close your browser.{% endblocktrans %}</p>
+
+ {% if url %}
+ <p>{% blocktrans %}You can return to service you came from: <a href="{{ url }}">{{ url }}</a>{% endblocktrans %}</p>
+ {% endif %}
+
+ {% url cas_provider.views.login as login_url %}
+ <p>{% blocktrans %}You can also <a href="{{ login_url }}">login again</a>{% endblocktrans %}</p>
+
+ {% include "horizontal_footer.html" %}
{% endblock %}
\ No newline at end of file
+++ /dev/null
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
- <title>{% block title %}Fundacja Nowoczesna Polska - CAS{% block subtitle %}{% endblock subtitle %}{% endblock title%}</title>
- {% block extrahead %}
- {% endblock %}
- </head>
- <body id="{% block bodyid %}base{% endblock %}">
- <div id="content">{% block content %} {% endblock %}</div>
- </body>
-</html>
--- /dev/null
+<hr />
+<p>
+ <a href="http://redakcja.nowoczesnapolska.org.pl/" >
+ <img src="http://www.nowoczesnapolska.org.pl/wordpress/wp-content/uploads/2008/11/wl-logo-white.png" class="small_logo">
+ </a>
+ <a href="http://redmine.nowoczesnapolska.org.pl/">
+ <img src="{{ MEDIA_URL }}static/redmine_logo.png" class="small_logo">
+ </a>
+</p>
\ No newline at end of file
# -*- coding: utf-8 -*-
from django.conf.urls.defaults import *
+from django.views.generic.simple import redirect_to
from django.contrib import admin
from django.conf import settings
admin.autodiscover()
urlpatterns = patterns('',
+ url(r'^$', redirect_to, {'url': '/accounts/'}),
+
+ # django-cas-provider
+ url(r'^cas/', include('cas_provider.urls')),
+
# Admin panel
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
- url(r'^admin/(.*)', admin.site.root),
+ url(r'^admin/', include(admin.site.urls)),
- # django-cas-provider
- url(r'^', include('cas_provider.urls')),
+ url(r'^accounts/', include('accounts.urls')),
+
+ url(r'^%s(?P<path>.+)$' % settings.MEDIA_URL[1:], 'django.views.static.serve',
+ {'document_root': settings.MEDIA_ROOT, 'show_indexes': True}),
)