Commit 903d075f authored by Sofia Papagiannaki's avatar Sofia Papagiannaki
Browse files

Merge branch 'master', remote-tracking branch 'origin' into aquarium

parents 963fde65 d01e7b22
......@@ -2,3 +2,4 @@ docs/build
*.db
*.pyc
.DS_Store
snf-astakos-app/astakos/version.py
README
======
Astakos is an identity management service, built by GRNET using Django (https://www.djangoproject.com/).
Learn more about Astakos at: http://code.grnet.gr/projects/astakos
Consult LICENSE for licensing information.
Documentation
-------------
All docs are in the docs/source directory. The .rst files are perfectly readable in source form.
To build the documentation you need to have Sphinx (http://sphinx.pocoo.org/) installed.
On a typical debian-based Linux system run:
apt-get install python-django python-django-south python-setuptools python-sphinx python-httplib2
Then run:
python setup.py build_sphinx
The documentation will be built in the docs/build/html directory.
Also run:
python setup.py build_sphinx -b text
Then find the plain text version of the docs in docs/build/text.
Running the server
------------------
Make sure you have all required packages installed:
apt-get install python-django python-django-south python-setuptools python-sphinx python-httplib2
Then run:
python manage.py syncdb
python manage.py migrate im
python manage.py loaddata admin_user
python manage.py runserver
Go to:
http://127.0.0.1:8000/im/admin?user=admin&token=0000
This server is useful during development, but should not be used for deployment.
To deploy Astakos using Apache, take a look at the Administrator Guide in docs.
UPGRADE
=======
\ No newline at end of file
{% extends "admin_base.html" %}
{% block body %}
<ul class="unstyled">
<li><strong>{{ stats.users }}</strong> User{{ stats.users|pluralize }} (<strong>{{ stats.pending }}</strong> pending)</li>
<li><strong>{{ stats.invitations }}</strong> Invitation{{ stats.invitations|pluralize }} sent (<strong>{{ stats.invitations_consumed }}</strong> consumed)</li>
</ul>
{% endblock body %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>{{ title|default:"Astakos Login" }}</title>
<link rel="stylesheet" href="{{ IM_MEDIA_URL }}css/bootstrap.css">
<script src="{{ IM_MEDIA_URL }}js/jquery.js"></script>
<script src="{{ IM_MEDIA_URL }}js/jquery.tablesorter.js"></script>
<script src="{{ IM_MEDIA_URL }}js/main.js"></script>
{% block head %}{% endblock %}
</head>
<body>
<div class="container">
<div style="padding: 5px 0px 0px 0px">
<img src="{{ IM_MEDIA_URL }}images/banner.png" width="900" height="200">
</div>
{% block title %}{% endblock %}
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="alert-message.{{ message.tags }}"{% endif %}>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% block tabs %}
<ul class="tabs">
<li{% ifequal tab "admin" %} class="active"{% endifequal %}>
<a href="{% url astakos.im.admin.views.admin %}">Home</a>
</li>
<li{% ifequal tab "users" %} class="active"{% endifequal %}>
<a href="{% url astakos.im.admin.views.users_list %}">Users</a>
</li>
<li{% ifequal tab "pending" %} class="active"{% endifequal %}>
<a href="{% url astakos.im.admin.views.pending_users %}">Pending Users</a>
</li>
<li{% ifequal tab "invitations" %} class="active"{% endifequal %}>
<a href="{% url astakos.im.admin.views.invitations_list %}">Invitations</a>
</li>
<li{% ifequal tab "logout" %} class="active"{% endifequal %}>
<a href="{% url astakos.im.views.logout %}">Logout</a>
</li>
</ul>
{% endblock %}
{% block body %}{% endblock %}
</div>
</body>
</html>
{% extends "admin_base.html" %}
{% load formatters %}
{% block body %}
<div class="row">
<div class="offset10 span3">
<form method="get">
<div class="input">
<input class="span3" name="filter" type="search" placeholder="search" value="{{ filter }}">
</div>
</form>
</div>
</div>
<table class="zebra-striped id-sorted">
<thead>
<tr>
<th>ID</th>
<th>Email</th>
<th>Real Name</th>
<th>Code</th>
<th>Inviter email</th>
<th>Inviter Real Name</th>
<th>Is consumed</th>
<th>Created</th>
<th>Consumed</th>
</tr>
</thead>
<tbody>
{% for inv in invitations %}
<tr>
<td>{{ inv.id }}</td>
<td>{{ inv.username }}</td>
<td>{{ inv.realname }}</td>
<td>{{ inv.code }}</td>
<td>{{ inv.inviter.email }}</td>
<td>{{ inv.inviter.realname }}</td>
<td>{{ inv.is_consumed }}</td>
<td>{{ inv.created }}</td>
<td>{{ inv.consumed }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if pages|length > 1 %}
<div class="pagination">
<ul>
{% if prev %}
<li class="prev"><a href="?page={{ prev }}{% if filter %}&filter={{ filter }}{% endif %}">&larr; Previous</a></li>
{% else %}
<li class="prev disabled"><a href="#">&larr; Previous</a></li>
{% endif %}
{% for p in pages %}
<li{% if page == p %} class="active"{% endif %}><a href="?page={{ p }}{% if filter %}&filter={{ filter }}{% endif %}">{{ p }}</a></li>
{% endfor %}
{% if next %}
<li class="next"><a href="?page={{ next }}{% if filter %}&filter={{ filter }}{% endif %}">&rarr; Next</a></li>
{% else %}
<li class="next disabled"><a href="#">&rarr; Next</a></li>
{% endif %}
</ul>
</div>
{% endif %}
<a class="btn success" href="{% url astakos.im.admin.views.invitations_export %}">Export</a>
<br /><br />
{% endblock body %}
{% extends "admin_base.html" %}
{% load formatters %}
{% block body %}
<div class="row">
<div class="offset10 span3">
<form method="get">
<div class="input">
<input class="span3" name="filter" type="search" placeholder="search" value="{{ filter }}">
</div>
</form>
</div>
</div>
<table class="zebra-striped id-sorted">
<thead>
<tr>
<th>ID</th>
<th>Email</th>
<th>Real Name</th>
<th>Affiliation</th>
<th>Email</th>
<th>Inviter</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.id }}</td>
<td>{{ user.email }}</td>
<td>{{ user.realname }}</td>
<td>{{ user.affiliation }}</td>
<td>{{ user.email }}</td>
<td>{{ user.inviter.realname }}</td>
<td>
<form action="{% url astakos.im.admin.views.users_activate user.id %}" method="post">{% csrf_token %}
<input type="hidden" name="page" value="{{ page }}">
<button type="submit" class="btn primary">Activate</button>
</form>
</tr>
{% endfor %}
</tbody>
</table>
{% if pages|length > 1 %}
<div class="pagination">
<ul>
{% if prev %}
<li class="prev"><a href="?page={{ prev }}{% if filter %}&filter={{ filter }}{% endif %}">&larr; Previous</a></li>
{% else %}
<li class="prev disabled"><a href="#">&larr; Previous</a></li>
{% endif %}
{% for p in pages %}
<li{% if page == p %} class="active"{% endif %}><a href="?page={{ p }}{% if filter %}&filter={{ filter }}{% endif %}">{{ p }}</a></li>
{% endfor %}
{% if next %}
<li class="next"><a href="?page={{ next }}{% if filter %}&filter={{ filter }}{% endif %}">&rarr; Next</a></li>
{% else %}
<li class="next disabled"><a href="#">&rarr; Next</a></li>
{% endif %}
</ul>
</div>
{% endif %}
<br /><br />
{% endblock body %}
{% extends "admin_base.html" %}
{% block body %}
<form action="{% url astakos.im.admin.views.users_create %}" method="post">{% csrf_token %}
{{ form.as_p }}
<div class="actions">
<button type="submit" class="btn primary">Create</button>
<button type="reset" class="btn">Reset</button>
</div>
</form>
{% endblock body %}
{% extends "admin_base.html" %}
{% load formatters %}
{% block body %}
<form action="{% url astakos.im.admin.views.users_modify user.id %}" method="post">{% csrf_token %}
{{ form.as_p }}
<div class="actions">
<button type="submit" class="btn primary">Save Changes</button>
<button type="reset" class="btn">Reset</button>
&nbsp;&nbsp;
<a class="btn danger needs-confirm" href="{% url astakos.im.admin.views.users_delete user.id %}">Delete User</a>
</div>
<div class="alert-message block-message error">
<p><strong>WARNING:</strong> Are you sure you want to delete this user?</p>
<div class="alert-actions">
<a class="btn danger" href="{% url astakos.im.admin.views.users_delete user.id %}">Delete</a>
<a class="btn alert-close">Cancel</a>
</div>
</div>
</form>
{% endblock body %}
{% extends "admin_base.html" %}
{% load formatters %}
{% block body %}
<div class="row">
<div class="offset10 span3">
<form method="get">
<div class="input">
<input class="span3" name="filter" type="search" placeholder="search" value="{{ filter }}">
</div>
</form>
</div>
</div>
<table class="zebra-striped id-sorted">
<thead>
<tr>
<th>ID</th>
<th>Email</th>
<th>Real Name</th>
<th>Admin</th>
<th>Affiliation</th>
<th>Is active?</th>
<th>Quota</th>
<th>Updated</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td><a href="{% url astakos.im.admin.views.users_info user.id %}">{{ user.id }}</a></td>
<td><a href="{% url astakos.im.admin.views.users_info user.id %}">{{ user.email }}</a></td>
<td>{{ user.realname }}</td>
<td>{{ user.is_superuser }}</td>
<td>{{ user.affiliation }}</td>
<td>{{ user.is_active }}</td>
<td>{{ user.quota|GiB }} GiB</td>
<td>{{ user.updated }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if pages|length > 1 %}
<div class="pagination">
<ul>
{% if prev %}
<li class="prev"><a href="?page={{ prev }}{% if filter %}&filter={{ filter }}{% endif %}">&larr; Previous</a></li>
{% else %}
<li class="prev disabled"><a href="#">&larr; Previous</a></li>
{% endif %}
{% for p in pages %}
<li{% if page == p %} class="active"{% endif %}><a href="?page={{ p }}{% if filter %}&filter={{ filter }}{% endif %}">{{ p }}</a></li>
{% endfor %}
{% if next %}
<li class="next"><a href="?page={{ next }}{% if filter %}&filter={{ filter }}{% endif %}">&rarr; Next</a></li>
{% else %}
<li class="next disabled"><a href="#">&rarr; Next</a></li>
{% endif %}
</ul>
</div>
{% endif %}
<a class="btn success" href="{% url astakos.im.admin.views.users_create %}">Create a user</a>
<a class="btn success" href="{% url astakos.im.admin.views.users_export %}">Export</a>
<br /><br />
{% endblock body %}
# Copyright 2011 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials
# provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
import logging
import socket
import csv
from functools import wraps
from math import ceil
from smtplib import SMTPException
from django.conf import settings
from django.core.mail import send_mail
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import redirect
from django.template.loader import render_to_string
from django.utils.http import urlencode
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
from django.contrib import messages
from django.db import transaction
from astakos.im.models import AstakosUser, Invitation
from astakos.im.util import get_context, get_current_site
from astakos.im.forms import *
from astakos.im.views import render_response, index
from astakos.im.admin.forms import AdminProfileForm
from astakos.im.admin.forms import AdminUserCreationForm
def requires_admin(func):
"""
Decorator checkes whether the request.user is a superuser and if not
redirects to login page.
"""
@wraps(func)
def wrapper(request, *args):
if not settings.BYPASS_ADMIN_AUTH:
if request.user.is_anonymous():
next = urlencode({'next': request.build_absolute_uri()})
login_uri = reverse(index) + '?' + next
return HttpResponseRedirect(login_uri)
if not request.user.is_superuser:
return HttpResponse('Forbidden', status=403)
return func(request, *args)
return wrapper
@requires_admin
def admin(request, template_name='admin.html', extra_context={}):
"""
Renders the admin page
If the ``request.user`` is not a superuser redirects to login page.
**Arguments**
``template_name``
A custom template to use. This is optional; if not specified,
this will default to ``admin.html``.
``extra_context``
An dictionary of variables to add to the template context.
**Template:**
admin.html or ``template_name`` keyword argument.
**Template Context:**
The template context is extended by:
* tab: the name of the active tab
* stats: dictionary containing the number of all and prending users
"""
stats = {}
stats['users'] = AstakosUser.objects.count()
stats['pending'] = AstakosUser.objects.filter(is_active = False).count()
invitations = Invitation.objects.all()
stats['invitations'] = invitations.count()
stats['invitations_consumed'] = invitations.filter(is_consumed=True).count()
kwargs = {'tab': 'home', 'stats': stats}
context = get_context(request, extra_context,**kwargs)
return render_response(template_name, context_instance = context)
@requires_admin
def users_list(request, template_name='users_list.html', extra_context={}):
"""
Displays the list of all users.
If the ``request.user`` is not a superuser redirects to login page.
**Arguments**
``template_name``
A custom template to use. This is optional; if not specified,
this will default to ``users_list.html``.
``extra_context``
An dictionary of variables to add to the template context.
**Template:**
users_list.html or ``template_name`` keyword argument.
**Template Context:**
The template context is extended by:
* users: list of users fitting in current page
* filter: search key
* pages: the number of pages
* prev: the previous page
* next: the current page
**Settings:**
* ADMIN_PAGE_LIMIT: Show these many users per page in admin interface
"""
users = AstakosUser.objects.order_by('id')
filter = request.GET.get('filter', '')
if filter:
if filter.startswith('-'):
users = users.exclude(username__icontains=filter[1:])
else:
users = users.filter(username__icontains=filter)
try:
page = int(request.GET.get('page', 1))
except ValueError:
page = 1
offset = max(0, page - 1) * settings.ADMIN_PAGE_LIMIT
limit = offset + settings.ADMIN_PAGE_LIMIT
npages = int(ceil(1.0 * users.count() / settings.ADMIN_PAGE_LIMIT))
prev = page - 1 if page > 1 else None
next = page + 1 if page < npages else None
kwargs = {'users':users[offset:limit],
'filter':filter,
'pages':range(1, npages + 1),
'prev':prev,
'next':next}
context = get_context(request, extra_context,**kwargs)
return render_response(template_name, context_instance = context)
@requires_admin
def users_info(request, user_id, template_name='users_info.html', extra_context={}):
"""
Displays the specific user profile.
If the ``request.user`` is not a superuser redirects to login page.
**Arguments**
``template_name``
A custom template to use. This is optional; if not specified,
this will default to ``users_info.html``.
``extra_context``
An dictionary of variables to add to the template context.
**Template:**
users_info.html or ``template_name`` keyword argument.
**Template Context:**
The template context is extended by:
* user: the user instance identified by ``user_id`` keyword argument
"""
if not extra_context:
extra_context = {}
user = AstakosUser.objects.get(id=user_id)
return render_response(template_name,
form = AdminProfileForm(instance=user),
user = user,
context_instance = get_context(request, extra_context))
@requires_admin
def users_modify(request, user_id, template_name='users_info.html', extra_context={}):
"""
Update the specific user information. Upon success redirects to ``user_info`` view.
If the ``request.user`` is not a superuser redirects to login page.
"""
user = AstakosUser.objects.get(id = user_id)
form = AdminProfileForm(request.POST, instance=user)
if form.is_valid():
form.save()
return users_info(request, user.id, template_name, extra_context)
return render_response(template_name,
form = form,
context_instance = get_context(request, extra_context))
@requires_admin
def users_delete(request, user_id):
"""
Deletes the specified user
If the ``request.user`` is not a superuser redirects to login page.
"""
user = AstakosUser.objects.get(id=user_id)
user.delete()
return redirect(users_list)
@requires_admin
def pending_users(request, template_name='pending_users.html', extra_context={}):
"""
Displays the list of the pending users.