Reworking of SSO app for CBVs

This commit is contained in:
2012-07-06 19:23:46 +01:00
parent 6b67525a42
commit 5f62f83855
8 changed files with 160 additions and 120 deletions

View File

@@ -111,6 +111,15 @@ class APIPasswordForm(forms.Form):
""" API Password reset form """ """ API Password reset form """
password = forms.CharField(widget=forms.PasswordInput, label="Password") password = forms.CharField(widget=forms.PasswordInput, label="Password")
password2 = forms.CharField(widget=forms.PasswordInput, label="Confirm Password")
def clean_password2(self):
password1 = self.cleaned_data.get('password')
password2 = self.cleaned_data.get('password2')
if password1 and password2:
if password1 != password2:
raise forms.ValidationError("The two passwords do not match.")
return password2
class EmailChangeForm(forms.Form): class EmailChangeForm(forms.Form):

View File

@@ -11,12 +11,13 @@
<p>This service will reset your password for all External Auth API Services. If you wish to continue please enter a new <p>This service will reset your password for all External Auth API Services. If you wish to continue please enter a new
password below.</p> password below.</p>
<form action="{% url sso.views.set_apipasswd %}" method="post"> <form action="{% url sso-apipassword %}" method="post">
<fieldset> <fieldset>
{% include "formtools/formerror.html" %} {% include "formtools/formerror.html" %}
{% include "formtools/formfield.html" with field=form.password %} {% include "formtools/formfield.html" with field=form.password %}
{% include "formtools/formfield.html" with field=form.password2 %}
{% csrf_token %} {% csrf_token %}
<input type="submit" value="Reset Account" class="btn primary" /> <input type="submit" value="Change Password" class="btn primary" />
</fieldset> </fieldset>
</form> </form>

View File

@@ -7,7 +7,7 @@
<h1>Change Email Address</h1> <h1>Change Email Address</h1>
</div> </div>
<form action="" method="post"> <form action="{% url sso-emailupdate %}" method="post">
<fieldset> <fieldset>
{% include "formtools/formerror.html" %} {% include "formtools/formerror.html" %}
{% include "formtools/formfield.html" with field=form.email1 %} {% include "formtools/formfield.html" with field=form.email1 %}

View File

@@ -3,7 +3,9 @@
{% block title %}Change your Primay Character{% endblock %} {% block title %}Change your Primay Character{% endblock %}
{% block content %} {% block content %}
<div class="page-header">
<h1>Change Your Primary Character</h1> <h1>Change Your Primary Character</h1>
</div>
<p>Your primary character is used across auth services to show as your display name and corporation. Please set this to which character you consider your "main".</p> <p>Your primary character is used across auth services to show as your display name and corporation. Please set this to which character you consider your "main".</p>

View File

@@ -33,14 +33,14 @@
<div class="active" id="overview"> <div class="active" id="overview">
<p> <p>
<b>Username:</b> {{ user.username }}<br/> <b>Username:</b> {{ user.username }}<br/>
<b>Email:</b> {{ user.email }} (<a href="{% url sso.views.email_change %}">change</a>)<br/> <b>Email:</b> {{ user.email }} (<a href="{% url sso-emailupdate %}">change</a>)<br/>
<b>Primary Character:</b> {{ user.get_profile.primary_character }} (<a href="{% url sso.views.primarychar_change %}">change</a>)<br/> <b>Primary Character:</b> {{ user.get_profile.primary_character }} (<a href="{% url sso-primarycharacterupdate %}">change</a>)<br/>
<b>Groups:</b> {{ user.groups.all|join:", " }}<br/> <b>Groups:</b> {{ user.groups.all|join:", " }}<br/>
</p> </p>
<h2>Passwords</h2> <h2>Passwords</h2>
<p> <p>
<a href="{% url django.contrib.auth.views.password_change %}" class="btn">Change Auth Login Password</a> <a href="{% url django.contrib.auth.views.password_change %}" class="btn">Change Auth Login Password</a>
<a href="{% url sso.views.set_apipasswd %}" class="btn">Change Services Password</a><br/><br/> <a href="{% url sso-apipassword %}" class="btn">Change Services Password</a><br/><br/>
<span class="label info">Note</span> Changing your services password will change it for all linked sites (forums, wiki, reimbursement tool) <span class="label info">Note</span> Changing your services password will change it for all linked sites (forums, wiki, reimbursement tool)
</p> </p>
@@ -71,7 +71,7 @@
</table> </table>
{% endif %} {% endif %}
<p> <p>
<a href="{% url sso.views.service_add %}" class="btn">Add Service</a> <a href="{% if available_services > 0 %}{% url sso.views.service_add %}{% else %}#{% endif %}" class="btn{% if available_services == 0 %} disabled{% endif %}">Add Service</a>
</p> </p>
{% endif %} {% endif %}
</div> </div>
@@ -144,9 +144,8 @@
{% endif %} {% endif %}
<p> <p>
<a href="{% url reddit-addaccount %}" class="btn">Add a Reddit account</a> <a href="{% url reddit-addaccount %}" class="btn">Add a Reddit account</a>
<a href="{% url sso-reddittagging %}" class="btn">{% if user.get_profile.tag_reddit_accounts %}Disable{% else %}Enable{% endif %} Reddit Flare</a>
</p> </p>
<p>Reddit account tagging is {% if user.get_profile.tag_reddit_accounts %}Enabled{% else %}Disabled{% endif %}. <a href="{% url sso.views.toggle_reddit_tagging %}">{% if user.get_profile.tag_reddit_accounts %}Disable{% else %}Enable{% endif %}</a></p>
</div> </div>
{% endifswitch %} {% endifswitch %}
{% endif %} {% endif %}

View File

@@ -13,5 +13,5 @@
you have recently joined a corporation, then please use the "Refresh" you have recently joined a corporation, then please use the "Refresh"
option on your API key</p> option on your API key</p>
<p><a href="{% url sso.views.profile %}">Return to your Profile</a></p> <p><a href="{% url sso-profile %}">Return to your Profile</a></p>
{% endblock %} {% endblock %}

View File

@@ -2,29 +2,30 @@ from django.conf.urls.defaults import *
from django.core.urlresolvers import reverse, reverse_lazy from django.core.urlresolvers import reverse, reverse_lazy
from django.contrib.auth.views import password_change, password_change_done from django.contrib.auth.views import password_change, password_change_done
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.views.generic import RedirectView
from sso import views from sso import views
urlpatterns = patterns('', urlpatterns = patterns('',
('^$', views.profile), url('^$', RedirectView.as_view(url=reverse_lazy('sso-profile'))),
url(r'^profile/$', views.profile, name='sso-profile'), url(r'^profile/$', views.ProfileView.as_view(), name='sso-profile'),
(r'^profile/add/service', views.service_add), (r'^profile/add/service', views.service_add),
(r'^profile/del/service/$', views.service_del), (r'^profile/del/service/$', views.service_del),
(r'^profile/del/service/(?P<serviceid>\d+)/$', views.service_del), (r'^profile/del/service/(?P<serviceid>\d+)/$', views.service_del),
(r'^profile/reset/service/(?P<serviceid>\d+)/$', views.service_reset), (r'^profile/reset/service/(?P<serviceid>\d+)/$', views.service_reset),
(r'^profile/reset/service/(?P<serviceid>\d+)/(?P<accept>\d+)$', views.service_reset), (r'^profile/reset/service/(?P<serviceid>\d+)/(?P<accept>\d+)$', views.service_reset),
(r'^profile/apipassword/', views.set_apipasswd), url(r'^profile/apipassword/', views.APIPasswordUpdateView.as_view(), name='sso-apipassword'),
(r'^profile/refresh/$', views.refresh_access), (r'^profile/refresh/$', views.refresh_access),
url(r'^profile/refresh/(?P<userid>\d+)/$', views.refresh_access, name='sso-refreshaccess'), url(r'^profile/refresh/(?P<userid>\d+)/$', views.refresh_access, name='sso-refreshaccess'),
(r'^profile/change/password/$', password_change), (r'^profile/change/password/$', password_change),
(r'^profile/change/email/$', views.email_change), url(r'^profile/change/email/$', views.EmailUpdateView.as_view(), name='sso-emailupdate'),
(r'^profile/change/primary/$', views.primarychar_change), url(r'^profile/change/primary/$', views.PrimaryCharacterUpdateView.as_view(), name='sso-primarycharacterupdate'),
(r'^profile/change/reddittag/$', views.toggle_reddit_tagging), url(r'^profile/change/reddittag/$', views.RedditTaggingUpdateView.as_view(), name='sso-reddittagging'),
(r'^users/$', views.user_lookup), (r'^users/$', views.user_lookup),
url(r'^users/(?P<username>.*)/addnote/$', login_required(views.AddUserNote.as_view()), name='sso-addusernote'), url(r'^users/(?P<username>.*)/addnote/$', views.AddUserNote.as_view(), name='sso-addusernote'),
url(r'^users/(?P<username>.*)/$', views.user_view, name='sso-viewuser'), url(r'^users/(?P<username>.*)/$', views.UserDetailView.as_view(), name='sso-viewuser'),
url(r'^address/$', login_required(views.UserIPAddressView.as_view()), name='sso-ipaddress'), url(r'^address/$', views.UserIPAddressView.as_view(), name='sso-ipaddress'),
) )
urlpatterns += patterns('django.views.generic.simple', urlpatterns += patterns('django.views.generic.simple',

View File

@@ -2,47 +2,53 @@ import hashlib
import random import random
import re import re
import unicodedata import unicodedata
import celery
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
from django.shortcuts import render_to_response, get_object_or_404, redirect from django.shortcuts import render_to_response, get_object_or_404, redirect
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse, reverse_lazy
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.template import RequestContext from django.template import RequestContext
from django.core import serializers
from django.conf import settings from django.conf import settings
from django.views.generic import FormView, ListView from django.views.generic import View, FormView, ListView, DetailView, TemplateView
import celery
from gargoyle import gargoyle from gargoyle import gargoyle
from gargoyle.decorators import switch_is_active from braces.views import LoginRequiredMixin
from utils import installed from utils import installed
from eve_api.models import EVEAccount, EVEPlayerCharacter from eve_api.models import EVEAccount, EVEPlayerCharacter
from eve_api.tasks import import_apikey, import_apikey_result, update_user_access from eve_api.tasks import import_apikey, import_apikey_result, update_user_access
from eve_proxy.models import ApiAccessLog from eve_proxy.models import ApiAccessLog
from reddit.tasks import update_user_flair
from sso.models import ServiceAccount, Service, SSOUser, ExistingUser, ServiceError, SSOUserIPAddress from sso.models import ServiceAccount, Service, SSOUser, ExistingUser, ServiceError, SSOUserIPAddress
from sso.forms import UserServiceAccountForm, ServiceAccountResetForm, UserLookupForm, APIPasswordForm, EmailChangeForm, PrimaryCharacterForm, UserNoteForm from sso.forms import UserServiceAccountForm, ServiceAccountResetForm, UserLookupForm, APIPasswordForm, EmailChangeForm, PrimaryCharacterForm, UserNoteForm
@login_required
def profile(request):
""" Displays the user's profile page """
try: class ProfileView(LoginRequiredMixin, TemplateView):
profile = request.user.get_profile()
except SSOUser.DoesNotExist:
profile = SSOUser(user=request.user)
profile.save()
if not profile.primary_character and EVEPlayerCharacter.objects.filter(eveaccount__user=request.user).count(): template_name = 'sso/profile.html'
return redirect('sso.views.primarychar_change')
context = { def get_profile(self, user):
'profile': request.user.get_profile() try:
} profile = user.get_profile()
return render_to_response('sso/profile.html', context, context_instance=RequestContext(request)) except SSOUser.DoesNotExist:
profile = SSOUser.objects.create(user=user)
return profile
def get(self, request, *args, **kwargs):
self.profile = self.get_profile(request.user)
if self.profile.primary_character is None and EVEPlayerCharacter.objects.filter(eveaccount__user=request.user).count():
return HttpResponseRedirect(reverse('sso.views.primarychar_change'))
return super(ProfileView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
ctx = super(ProfileView, self).get_context_data(**kwargs)
ctx.update({
'profile': self.profile,
'available_services': Service.objects.filter(groups__in=self.request.user.groups.all()).exclude(id__in=ServiceAccount.objects.filter(user=self.request.user).values('service')).count()
})
return ctx
@login_required @login_required
@@ -86,8 +92,8 @@ def service_add(request):
return render_to_response('sso/serviceaccount/created.html', locals(), context_instance=RequestContext(request)) return render_to_response('sso/serviceaccount/created.html', locals(), context_instance=RequestContext(request))
else: else:
availserv = Service.objects.filter(groups__in=request.user.groups.all()).exclude(id__in=ServiceAccount.objects.filter(user=request.user).values('service')) availserv = Service.objects.filter(groups__in=request.user.groups.all()).exclude(id__in=ServiceAccount.objects.filter(user=request.user).values('service')).count()
if len(availserv) == 0: if not availserv:
return render_to_response('sso/serviceaccount/noneavailable.html', locals(), context_instance=RequestContext(request)) return render_to_response('sso/serviceaccount/noneavailable.html', locals(), context_instance=RequestContext(request))
else: else:
form = clsform() # An unbound form form = clsform() # An unbound form
@@ -153,29 +159,37 @@ def service_reset(request, serviceid, template='sso/serviceaccount/reset.html',
return render_to_response(template, locals(), context_instance=RequestContext(request)) return render_to_response(template, locals(), context_instance=RequestContext(request))
@login_required class UserDetailView(LoginRequiredMixin, DetailView):
def user_view(request, username, template='sso/lookup/user.html'):
""" View a user's profile as a admin """
if not request.user.has_perm('sso.can_view_users') and not request.user.has_perm('sso.can_view_users_restricted'): model = User
return redirect('sso.views.profile') slug_url_kwarg = 'username'
slug_field = 'username'
template_name = 'sso/lookup/user.html'
user = get_object_or_404(User, username=username) def get(self, request, *args, **kwargs):
if not request.user.has_perm('sso.can_view_users') and not request.user.has_perm('sso.can_view_users_restricted'):
return HttpResponseForbidden()
return super(UserDetailView, self).get(request, *args, **kwargs)
context = { def get_context_data(self, **kwargs):
'user': user, ctx = super(UserDetailView, self).get_context_data(**kwargs)
'profile': user.get_profile(), ctx.update({
'services': ServiceAccount.objects.select_related('service').filter(user=user).only('service__name', 'service_uid', 'active'), 'profile': self.object.get_profile(),
'characters': EVEPlayerCharacter.objects.select_related('corporation').filter(eveaccount__user=user).only('id', 'name', 'corporation__name'), 'services': ServiceAccount.objects.select_related('service').filter(user=self.object).only('service__name', 'service_uid', 'active'),
} 'characters': EVEPlayerCharacter.objects.select_related('corporation', 'corporation__alliance').filter(eveaccount__user=self.object).only('id', 'name', 'corporation__name'),
})
# If the HR app is installed, check the blacklist # If the HR app is installed, check the blacklist
if installed('hr'): if installed('hr'):
if request.user.has_perm('hr.add_blacklist'): if self.request.user.has_perm('hr.add_blacklist'):
from hr.utils import blacklist_values from hr.utils import blacklist_values
context['blacklisted'] = len(blacklist_values(user)) output = blacklist_values(self.object)
ctx.update({
'blacklisted': bool(len(output)),
'blacklist_items': output,
})
return render_to_response(template, context, context_instance=RequestContext(request)) return ctx
@login_required @login_required
@@ -216,7 +230,7 @@ def user_lookup(request):
return redirect('sso.views.user_lookup') return redirect('sso.views.user_lookup')
if users and len(users) == 1: if users and len(users) == 1:
return redirect(user_view, username=users[0].username) return redirect('sso-viewuser', username=users[0].username)
elif users and len(users) > 1: elif users and len(users) > 1:
return render_to_response('sso/lookup/lookuplist.html', locals(), context_instance=RequestContext(request)) return render_to_response('sso/lookup/lookuplist.html', locals(), context_instance=RequestContext(request))
else: else:
@@ -226,22 +240,18 @@ def user_lookup(request):
return render_to_response('sso/lookup/userlookup.html', locals(), context_instance=RequestContext(request)) return render_to_response('sso/lookup/userlookup.html', locals(), context_instance=RequestContext(request))
@login_required class APIPasswordUpdateView(LoginRequiredMixin, FormView):
def set_apipasswd(request):
""" Sets the user's auth API password """
if request.method == 'POST': form_class = APIPasswordForm
form = APIPasswordForm(request.POST) template_name = 'sso/apipassword.html'
if form.is_valid(): success_url = reverse_lazy('sso-profile')
profile = request.user.get_profile()
profile.api_service_password = hashlib.sha1(form.cleaned_data['password']).hexdigest()
profile.save()
messages.add_message(request, messages.INFO, "Your API Services password has been set.")
return redirect('sso.views.profile') # Redirect after POST
else:
form = APIPasswordForm() # An unbound form
return render_to_response('sso/apipassword.html', locals(), context_instance=RequestContext(request)) def form_valid(self, form):
profile = request.user.get_profile()
profile.api_service_password = hashlib.sha1(form.cleaned_data['password']).hexdigest()
profile.save()
message.success(self.request, "Your API services password has been updated.")
return super(APIPasswordUpdateView, self).form_valid(form)
@login_required @login_required
@@ -271,66 +281,82 @@ def refresh_access(request, userid=0, corpid=0, allianceid=0):
return redirect('sso.views.profile') return redirect('sso.views.profile')
@login_required class EmailUpdateView(LoginRequiredMixin, FormView):
def email_change(request): """Updates a user's email address"""
""" Change the user's email address """
if request.method == 'POST': form_class = EmailChangeForm
form = EmailChangeForm(request.POST) template_name = 'sso/emailchange.html'
if form.is_valid(): success_url = reverse_lazy('sso-profile')
request.user.email = form.cleaned_data['email2']
request.user.save()
messages.add_message(request, messages.INFO, "E-mail address changed to %s." % form.cleaned_data['email2'])
return redirect('sso.views.profile') # Redirect after POST
else:
form = EmailChangeForm() # An unbound form
return render_to_response('sso/emailchange.html', locals(), context_instance=RequestContext(request)) def form_valid(self, form):
request.user.email = form.cleaned_data['email2']
@login_required request.user.save()
def primarychar_change(request): messages.success(self.request, "E-mail address changed to %s." % form.cleaned_data['email2'])
""" Change the user's primary character """ return super(EmailUpdateView).form_valid(form)
if request.method == 'POST':
form = PrimaryCharacterForm(request.POST, user=request.user)
if form.is_valid():
profile = request.user.get_profile()
profile.primary_character = form.cleaned_data['character']
profile.save()
messages.add_message(request, messages.INFO, "Your primary character has changed to %s." % form.cleaned_data['character'])
return redirect('sso.views.profile') # Redirect after POST
else:
form = PrimaryCharacterForm(initial={'character': request.user.get_profile().primary_character}, user=request.user) # An unbound form
return render_to_response('sso/primarycharchange.html', locals(), context_instance=RequestContext(request))
@login_required class PrimaryCharacterUpdateView(LoginRequiredMixin, FormView):
@switch_is_active('reddit') """Updates a user's primary character selection"""
def toggle_reddit_tagging(request):
profile = request.user.get_profile() form_class = PrimaryCharacterForm
if profile.primary_character: template_name = 'sso/primarycharchange.html'
success_url = reverse_lazy('sso-profile')
def get_form_kwargs(self):
kwargs = super(PrimaryCharacterUpdateView, self).get_form_kwargs()
kwargs.update({
'user': self.request.user
})
return kwargs
def get_initial(self):
initial = super(PrimaryCharacterUpdateView, self).get_initial()
initial.update({
'character': self.request.user.get_profile().primary_character
})
return initial
def form_valid(self, form):
profile = self.request.user.get_profile()
profile.primary_character = form.cleaned_data['character']
profile.save()
messages.success(self.request, "Your primary character has changed to %s." % form.cleaned_data['character'])
return super(PrimaryCharacterUpdateView, self).form_valid(form)
class RedditTaggingUpdateView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
if not gargoyle.is_active('reddit', request):
return HttpResponseNotFound()
profile = request.user.get_profile()
if profile.primary_character is None:
messages.error("Reddit account tagging requires a primary character before using. Please set one.")
if EVEPlayerCharacter.objects.filter(eveaccount__user=request.user).count():
return HttpResponseRedirect(reverse('sso-primarycharacterupdate'))
else:
return HttpResponseRedirect(reverse('sso-profile'))
profile.tag_reddit_accounts = not profile.tag_reddit_accounts profile.tag_reddit_accounts = not profile.tag_reddit_accounts
profile.save() profile.save()
if profile.tag_reddit_accounts: if profile.tag_reddit_accounts:
tag = 'Enabled' tag = 'Enabled'
else: else:
tag = 'Disabled' tag = 'Disabled'
messages.add_message(request, messages.INFO, "Reddit account tagging is now %s" % tag) messages.info(request, "Reddit account tagging is now %s" % tag)
if profile.tag_reddit_accounts: if profile.tag_reddit_accounts:
name = profile.primary_character.name name = profile.primary_character.name
else: else:
name = '' name = ''
for acc in request.user.redditaccount_set.all(): for acc in request.user.redditaccount_set.all():
from reddit.tasks import update_user_flair
update_user_flair.delay(acc.username, name) update_user_flair.delay(acc.username, name)
else: return HttpResponseRedirect(reverse('sso-profile'))
messages.add_message(request, messages.ERROR, "You need to set a primary character before using this feature!")
return redirect('sso.views.profile')
class AddUserNote(FormView): class AddUserNote(LoginRequiredMixin, FormView):
template_name = 'sso/add_usernote.html' template_name = 'sso/add_usernote.html'
form_class = UserNoteForm form_class = UserNoteForm
@@ -348,27 +374,29 @@ class AddUserNote(FormView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super(AddUserNote, self).get_context_data(**kwargs) ctx = super(AddUserNote, self).get_context_data(**kwargs)
ctx['user'] = self.get_user() ctx.update({
'user': self.get_user()
})
return ctx return ctx
def get_initial(self): def get_initial(self):
initial = super(AddUserNote, self).get_initial() initial = super(AddUserNote, self).get_initial()
initial['user'] = self.get_user() initial.update({
'user': self.get_user()
})
return initial return initial
def get_success_url(self): def get_success_url(self):
return reverse('sso-viewuser', args=[self.get_user()]) return reverse('sso-viewuser', args=[self.get_user()])
def form_valid(self, form): def form_valid(self, form):
obj = form.save(commit=False) obj = form.save(commit=False)
obj.created_by = self.request.user obj.created_by = self.request.user
obj.save() obj.save()
return super(AddUserNote, self).form_valid(form) return super(AddUserNote, self).form_valid(form)
class UserIPAddressView(ListView): class UserIPAddressView(LoginRequiredMixin, ListView):
model = SSOUserIPAddress model = SSOUserIPAddress