Merge pull request #85 from SunilMohanAdapa/django-auth

Django auth
This commit is contained in:
Nick Daly 2014-07-07 00:37:28 +00:00
commit 9b1f2c6f0d
29 changed files with 123 additions and 547 deletions

2
.gitignore vendored
View File

@ -24,7 +24,7 @@ TODO
\#*
.#*
*~
data/users.sqlite3
data/plinth.sqlite3
predepend
build/
*.pid

View File

@ -1,4 +1,4 @@
# License to Copy Plinth
# License to Copy Plinth
Plinth is Copyright 2011-2013 James Vasile (<james@hackervisions.org>). It
is distributed under the GNU Affero General Public License, Version 3
@ -16,10 +16,6 @@ The documentation to this software is also distributed under the [GNU
Free Documentation License](http://www.gnu.org/licenses/fdl.html),
version 1.3 or later.
In default form, Plinth incorporates FileDict, a Python module
released under a "MIT/BSD/Python" license, as per [its blog
page](https://erezsh.wordpress.com/2009/05/31/filedict-bug-fixes-and-updates/).
## GNU Affero General Public License, Version 3
GNU AFFERO GENERAL PUBLIC LICENSE

View File

@ -9,18 +9,15 @@ specified and linked otherwise.
- COPYING :: N/A
- COPYRIGHTS :: N/A
- fabfile.py :: -
- filedict.py :: [[http://erez.wikidot.com/filedict-0-1-code][CC-BY-SA 3.0]]
- INSTALL :: -
- logger.py :: -
- Makefile :: -
- menu.py :: -
- model.py :: -
- NOTES :: -
- plinth :: -
- plinth.config :: -
- plinth.py :: [[file:plinth.py::__license__%20%3D%20"GPLv3%20or%20later"]["GPLv3 or later"]]
- plinth.sample.config :: -
- plugin_mount.py :: [[http://martyalchin.com/2008/jan/10/simple-plugin-framework/][CC-BY-SA 3.0]]
- README :: -
- start.sh :: -
- test.sh :: -
@ -51,9 +48,7 @@ specified and linked otherwise.
- modules/expert_mode/expert_mode.py :: -
- modules/first_boot/first_boot.py :: -
- modules/help/help.py :: -
- modules/lib/auth_page.py :: -
- modules/lib/auth.py :: -
- modules/lib/user_store.py :: -
- modules/owncloud/owncloud.py :: -
- modules/packages/packages.py :: -
- modules/santiago/santiago.py :: -

2
cfg.py
View File

@ -19,7 +19,7 @@ host = None
port = None
debug = False
no_daemon = False
session_key = '_username'
server_dir = ''
main_menu = Menu()

View File

@ -1,152 +0,0 @@
"""filedict.py
a Persistent Dictionary in Python
Author: Erez Shinan
Date : 31-May-2009
"""
import json
import UserDict
import sqlite3
class DefaultArg(object):
pass
class Solutions(object):
Sqlite3 = 0
class FileDict(UserDict.DictMixin):
"A dictionary that stores its data persistantly in a file"
def __init__(self, solution=Solutions.Sqlite3, **options):
assert solution == Solutions.Sqlite3
try:
self.__conn = options.pop('connection')
except KeyError:
filename = options.pop('filename')
self.__conn = sqlite3.connect(filename)
self.__tablename = options.pop('table', 'dict')
self._nocommit = False
assert not options, "Unrecognized options: %s" % options
self.__conn.execute('create table if not exists %s (id integer primary key, hash integer, key blob, value blob);'%self.__tablename)
self.__conn.execute('create index if not exists %s_index ON %s(hash);' % (self.__tablename, self.__tablename))
self.__conn.commit()
def _commit(self):
if self._nocommit:
return
self.__conn.commit()
def __pack(self, value):
return sqlite3.Binary(json.dumps(value))
##return sqlite3.Binary(pickle.dumps(value, -1))
def __unpack(self, value):
return json.loads(str(value))
##return pickle.loads(str(value))
def __get_id(self, key):
cursor = self.__conn.execute('select key,id from %s where hash=?;'%self.__tablename, (hash(key),))
for k,id in cursor:
if self.__unpack(k) == key:
return id
raise KeyError(key)
def __getitem__(self, key):
cursor = self.__conn.execute('select key,value from %s where hash=?;'%self.__tablename, (hash(key),))
for k,v in cursor:
if self.__unpack(k) == key:
return self.__unpack(v)
raise KeyError(key)
def __setitem(self, key, value):
value_pickle = self.__pack(value)
try:
id = self.__get_id(key)
cursor = self.__conn.execute('update %s set value=? where id=?;'%self.__tablename, (value_pickle, id) )
except KeyError:
key_pickle = self.__pack(key)
cursor = self.__conn.execute('insert into %s (hash, key, value) values (?, ?, ?);'
%self.__tablename, (hash(key), key_pickle, value_pickle) )
assert cursor.rowcount == 1
def __setitem__(self, key, value):
self.__setitem(key, value)
self._commit()
def __delitem__(self, key):
id = self.__get_id(key)
cursor = self.__conn.execute('delete from %s where id=?;'%self.__tablename, (id,))
if cursor.rowcount <= 0:
raise KeyError(key)
self._commit()
def update(self, d):
for k,v in d.iteritems():
self.__setitem(k, v)
self._commit()
def __iter__(self):
return (self.__unpack(x[0]) for x in self.__conn.execute('select key from %s;'%self.__tablename) )
def keys(self):
return iter(self)
def values(self):
return (self.__unpack(x[0]) for x in self.__conn.execute('select value from %s;'%self.__tablename) )
def items(self):
return (map(self.__unpack, x) for x in self.__conn.execute('select key,value from %s;'%self.__tablename) )
def iterkeys(self):
return self.keys()
def itervalues(self):
return self.values()
def iteritems(self):
return self.items()
def __contains__(self, key):
try:
self.__get_id(key)
return True
except KeyError:
return False
def __len__(self):
return self.__conn.execute('select count(*) from %s;' % self.__tablename).fetchone()[0]
def __del__(self):
try:
self.__conn
except AttributeError:
pass
else:
self.__conn.commit()
@property
def batch(self):
return self._Batch(self)
class _Batch(object):
def __init__(self, d):
self.__d = d
def __enter__(self):
self.__old_nocommit = self.__d._nocommit
self.__d._nocommit = True
return self.__d
def __exit__(self, type, value, traceback):
self.__d._nocommit = self.__old_nocommit
self.__d._commit()
return True

View File

@ -14,11 +14,7 @@ def init():
class Logger(object):
"""By convention, log levels are DEBUG, INFO, WARNING, ERROR and CRITICAL."""
def log(self, msg, level="DEBUG"):
try:
username = cherrypy.session.get(cfg.session_key)
except AttributeError:
username = ''
cherrypy.log.error("%s %s %s" % (username, level, msg), inspect.stack()[2][3], 20)
cherrypy.log.error("%s %s" % (level, msg), inspect.stack()[2][3], 20)
def __call__(self, *args):
self.log(*args)

View File

@ -1,17 +0,0 @@
class User(dict):
""" Every user must have keys for a username, name, passphrase (this
is a bcrypt hash of the password), salt, groups, and an email address.
They can be blank or None, but the keys must exist. """
def __init__(self, dict=None):
super(User, self).__init__()
for key in ['username', 'name', 'passphrase', 'salt', 'email']:
self[key] = ''
for key in ['groups']:
self[key] = []
if dict:
for key in dict:
self[key] = dict[key]
def __getattr__(self, attr):
return None

View File

@ -21,6 +21,7 @@ Plinth module for configuring timezone, hostname etc.
from django import forms
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core import validators
from django.template.response import TemplateResponse
from gettext import gettext as _
@ -29,7 +30,6 @@ import socket
import actions
import cfg
from ..lib.auth import login_required
import util
@ -102,7 +102,7 @@ def index(request):
form = None
is_expert = cfg.users.expert(request=request)
is_expert = request.user.groups.filter(name='Expert').exists()
if request.method == 'POST' and is_expert:
form = ConfigurationForm(request.POST, prefix='configuration')
# pylint: disable-msg=E1101
@ -160,9 +160,6 @@ def set_hostname(hostname):
actions.superuser_run("xmpp-pre-hostname-change")
actions.superuser_run("hostname-change", hostname)
actions.superuser_run("xmpp-hostname-change", hostname, async=True)
# don't persist/cache change unless it was saved successfuly
sys_store = util.filedict_con(cfg.store_file, 'sys')
sys_store['hostname'] = hostname
except OSError:
return False

View File

@ -19,12 +19,12 @@
Plinth module for running diagnostics
"""
from gettext import gettext as _
from django.contrib.auth.decorators import login_required
from django.template.response import TemplateResponse
from gettext import gettext as _
import actions
import cfg
from ..lib.auth import login_required
def init():

View File

@ -1,10 +1,11 @@
from django import forms
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.template.response import TemplateResponse
from gettext import gettext as _
import cfg
from ..lib.auth import login_required
from ..lib.auth import get_group
class ExpertsForm(forms.Form): # pylint: disable-msg=W0232
@ -43,23 +44,21 @@ def index(request):
def get_status(request):
"""Return the current status"""
return {'expert_mode': cfg.users.expert(request=request)}
return {'expert_mode': request.user.groups.filter(name='Expert').exists()}
def _apply_changes(request, new_status):
"""Apply expert mode configuration"""
message = (messages.info, _('Settings unchanged'))
user = cfg.users.current(request=request)
expert_group = get_group('Expert')
if new_status['expert_mode']:
if not 'expert' in user['groups']:
user['groups'].append('expert')
if not expert_group in request.user.groups.all():
request.user.groups.add(expert_group)
message = (messages.success, _('Expert mode enabled'))
else:
if 'expert' in user['groups']:
user['groups'].remove('expert')
if expert_group in request.user.groups.all():
request.user.groups.remove(expert_group)
message = (messages.success, _('Expert mode disabled'))
cfg.users.set(user['username'], user)
message[0](request, message[1])

View File

@ -19,12 +19,12 @@
Plinth module to configure a firewall
"""
from django.contrib.auth.decorators import login_required
from django.template.response import TemplateResponse
from gettext import gettext as _
import actions
import cfg
from ..lib.auth import login_required
import service as service_module

View File

@ -20,9 +20,5 @@ Plinth library modules
"""
from . import auth
from . import auth_page
from . import user_store
__all__ = ['auth',
'auth_page',
'user_store']
__all__ = ['auth']

View File

@ -1,98 +1,29 @@
from django.http.response import HttpResponseRedirect
import functools
from passlib.hash import bcrypt
from passlib.exc import PasswordSizeError
import cfg
from model import User
from django.contrib.auth.models import Group, User
def add_user(username, passphrase, name='', email='', expert=False):
"""Add a new user with specified username and passphrase.
"""
error = None
if not username: error = "Must specify a username!"
if not passphrase: error = "Must specify a passphrase!"
"""Add a new user with specified username and passphrase"""
if not username:
return 'Must specify a username!'
if error is None:
if username in map(lambda x: x[0], cfg.users.get_all()):
error = "User already exists!"
else:
try:
pass_hash = bcrypt.encrypt(passphrase)
except PasswordSizeError:
error = "Password is too long."
if not passphrase:
return 'Must specify a passphrase!'
if error is None:
di = {
'username':username,
'name':name,
'email':email,
'expert':'on' if expert else 'off',
'groups':['expert'] if expert else [],
'passphrase':pass_hash,
'salt':pass_hash[7:29], # for bcrypt
}
new_user = User(di)
cfg.users.set(username,new_user)
user = User.objects.create_user(username, email=email,
password=passphrase)
user.first_name = name
user.save()
if error:
cfg.log(error)
return error
if expert:
user.groups.add(get_group('Expert'))
def check_credentials(username, passphrase):
"""Verifies credentials for username and passphrase.
Returns None on success or a string describing the error on failure.
Handles passwords up to 4096 bytes:
>>> len("A" * 4096)
4096
>>> len(u"u|2603" * 682)
4092
"""
if not username or not passphrase:
error = "No username or password."
cfg.log(error)
return error
bad_authentication = "Bad username or password."
hashed_password = None
if username not in cfg.users or 'passphrase' not in cfg.users[username]:
cfg.log(bad_authentication)
return bad_authentication
hashed_password = cfg.users[username]['passphrase']
def get_group(name):
"""Return an existing or newly created group with given name"""
try:
# time-dependent comparison when non-ASCII characters are used.
if not bcrypt.verify(passphrase, hashed_password):
error = bad_authentication
else:
error = None
except PasswordSizeError:
error = bad_authentication
group = Group.objects.get(name__exact=name)
except Group.DoesNotExist:
group = Group(name=name)
group.save()
if error:
cfg.log(error)
return error
# XXX: Only required until we start using Django authentication system properly
def login_required(func):
"""A decorator to ensure that user is logged in before accessing a view"""
@functools.wraps(func)
def wrapper(request, *args, **kwargs):
"""Check that user is logged in"""
if not request.session.get(cfg.session_key, None):
return HttpResponseRedirect(
cfg.server_dir + "/auth/login?from_page=%s" % request.path)
return func(request, *args, **kwargs)
return wrapper
return group

View File

@ -1,67 +0,0 @@
"""
Controller to provide login and logout actions
"""
import cfg
from django import forms
from django.http.response import HttpResponseRedirect
from django.template.response import TemplateResponse
from gettext import gettext as _
from . import auth
class LoginForm(forms.Form): # pylint: disable-msg=W0232
"""Login form"""
username = forms.CharField(label=_('Username'))
password = forms.CharField(label=_('Passphrase'),
widget=forms.PasswordInput())
def clean(self):
"""Check for valid credentials"""
# pylint: disable-msg=E1101
if 'username' in self._errors or 'password' in self._errors:
return self.cleaned_data
error_msg = auth.check_credentials(self.cleaned_data['username'],
self.cleaned_data['password'])
if error_msg:
raise forms.ValidationError(error_msg, code='invalid_credentials')
return self.cleaned_data
def login(request):
"""Serve the login page"""
form = None
if request.method == 'POST':
form = LoginForm(request.POST, prefix='auth')
# pylint: disable-msg=E1101
if form.is_valid():
username = form.cleaned_data['username']
request.session[cfg.session_key] = username
return HttpResponseRedirect(_get_from_page(request))
else:
form = LoginForm(prefix='auth')
return TemplateResponse(request, 'form.html',
{'title': _('Login'),
'form': form,
'submit_text': _('Login')})
def logout(request):
"""Logout and redirect to origin page"""
try:
del request.session[cfg.session_key]
request.session.flush()
except KeyError:
pass
return HttpResponseRedirect(_get_from_page(request))
def _get_from_page(request):
"""Return the 'from page' of a request"""
return request.GET.get('from_page', cfg.server_dir + '/')

View File

@ -21,9 +21,13 @@ URLs for the Lib module
from django.conf.urls import patterns, url
import cfg
urlpatterns = patterns( # pylint: disable-msg=C0103
'modules.lib.auth_page',
url(r'^auth/login/$', 'login'),
url(r'^auth/logout/$', 'logout')
'',
url(r'^accounts/login/$', 'django.contrib.auth.views.login',
{'template_name': 'login.html'}),
url(r'^accounts/logout/$', 'django.contrib.auth.views.logout',
{'next_page': cfg.server_dir})
)

View File

@ -1,73 +0,0 @@
import cfg
from model import User
from plugin_mount import UserStoreModule
from withsqlite.withsqlite import sqlite_db
class UserStore(UserStoreModule, sqlite_db):
def __init__(self):
super(UserStore, self).__init__()
self.db_file = cfg.user_db
sqlite_db.__init__(self, self.db_file, autocommit=True, check_same_thread=False)
self.__enter__()
def close(self):
self.__exit__(None,None,None)
def current(self, request=None, name=False):
"""Return current user, if there is one, else None.
If name = True, return the username instead of the user."""
if not request:
return None
try:
username = request.session[cfg.session_key]
except KeyError:
return None
if name:
return username
return self.get(username)
def expert(self, username=None, request=None):
"""Return whether the current or provided user is an expert"""
if not username:
if not request:
return False
username = self.current(request=request, name=True)
groups = self.attr(username, 'groups')
if not groups:
return False
return 'expert' in groups
def attr(self, username=None, field=None):
return self.get(username)[field]
def get(self,username=None):
return User(sqlite_db.get(self,username))
def exists(self, username=None):
try:
user = self.get(username)
if not user:
return False
elif user["username"]=='':
return False
return True
except TypeError:
return False
def remove(self,username=None):
self.__delitem__(username)
def get_all(self):
return self.items()
def set(self,username=None,user=None):
sqlite_db.__setitem__(self,username, user)

View File

@ -1,11 +1,11 @@
from django import forms
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.template.response import TemplateResponse
from gettext import gettext as _
import actions
import cfg
from ..lib.auth import login_required
import service

View File

@ -1,11 +1,11 @@
from django import forms
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.template.response import TemplateResponse
from gettext import gettext as _
import actions
import cfg
from ..lib.auth import login_required
def get_modules_available():

View File

@ -21,6 +21,7 @@ Plinth module for configuring PageKite service
from django import forms
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core import validators
from django.template import RequestContext
from django.template.loader import render_to_string
@ -29,7 +30,6 @@ from gettext import gettext as _
import actions
import cfg
from ..lib.auth import login_required
def init():

View File

@ -19,12 +19,12 @@
Plinth module for configuring Tor
"""
from django.contrib.auth.decorators import login_required
from django.template.response import TemplateResponse
from gettext import gettext as _
import actions
import cfg
from ..lib.auth import login_required
def init():

View File

@ -1,5 +1,7 @@
from django import forms
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.core import validators
from django.template import RequestContext
from django.template.loader import render_to_string
@ -7,8 +9,7 @@ from django.template.response import TemplateResponse
from gettext import gettext as _
import cfg
from ..lib.auth import add_user, login_required
from model import User
from ..lib.auth import add_user
def init():
@ -72,7 +73,7 @@ def add(request):
def _add_user(request, data):
"""Add a user"""
if cfg.users.exists(data['username']):
if User.objects.filter(username=data['username']).exists():
messages.error(request, _('User "{username}" already exists').format(
username=data['username']))
return
@ -89,14 +90,11 @@ class UserEditForm(forms.Form): # pylint: disable-msg=W0232
# pylint: disable-msg=E1002
super(forms.Form, self).__init__(*args, **kwargs)
users = cfg.users.get_all()
for uname in users:
user = User(uname[1])
label = '%s (%s)' % (user['name'], user['username'])
for user in User.objects.all():
label = '%s (%s)' % (user.first_name, user.username)
field = forms.BooleanField(label=label, required=False)
# pylint: disable-msg=E1101
self.fields['delete_user_' + user['username']] = field
self.fields['delete_user_' + user.username] = field
@login_required
@ -129,21 +127,21 @@ def _apply_edit_changes(request, data):
username = field.split('delete_user_')[1]
requesting_user = request.session.get(cfg.session_key, None)
requesting_user = request.user.username
cfg.log.info('%s asked to delete %s' %
(requesting_user, username))
if username == cfg.users.current(request=request, name=True):
if username == requesting_user:
messages.error(
request, _('Can not delete current account - "%s"') % username)
continue
if not cfg.users.exists(username):
if not User.objects.filter(username=username).exists():
messages.error(request, _('User "%s" does not exist') % username)
continue
try:
cfg.users.remove(username)
User.objects.filter(username=username).delete()
messages.success(request, _('User "%s" deleted') % username)
except IOError as exception:
messages.error(request, _('Error deleting "%s" - %s') %

View File

@ -1,5 +1,6 @@
from django import forms
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.template import RequestContext
from django.template.loader import render_to_string
from django.template.response import TemplateResponse
@ -7,7 +8,6 @@ from gettext import gettext as _
import actions
import cfg
from ..lib.auth import login_required
import service

View File

@ -1,10 +1,12 @@
#!/usr/bin/env python
import argparse
import os
import sys
import django.conf
import django.core.management
import django.core.wsgi
import os
import stat
import sys
import cherrypy
from cherrypy import _cpserver
@ -12,7 +14,6 @@ from cherrypy.process.plugins import Daemonizer
import cfg
import module_loader
import plugin_mount
import service
import logger
@ -112,7 +113,6 @@ def context_processor(request):
'submenu': cfg.main_menu.active_item(request),
'request_path': request.path,
'basehref': cfg.server_dir,
'username': request.session.get(cfg.session_key, None),
'active_menu_urls': active_menu_urls
}
@ -129,19 +129,36 @@ def configure_django():
'django.contrib.messages.context_processors.messages',
'plinth.context_processor']
data_file = os.path.join(cfg.data_dir, 'plinth.sqlite3')
template_directories = module_loader.get_template_directories()
sessions_directory = os.path.join(cfg.data_dir, 'sessions')
django.conf.settings.configure(
DEBUG=cfg.debug,
ALLOWED_HOSTS=['127.0.0.1', 'localhost'],
TEMPLATE_DIRS=template_directories,
CACHES={'default':
{'BACKEND': 'django.core.cache.backends.dummy.DummyCache'}},
DATABASES={'default':
{'ENGINE': 'django.db.backends.sqlite3',
'NAME': data_file}},
DEBUG=cfg.debug,
INSTALLED_APPS=['bootstrapform',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.messages'],
LOGIN_URL=cfg.server_dir + '/accounts/login/',
LOGIN_REDIRECT_URL=cfg.server_dir + '/',
LOGOUT_URL=cfg.server_dir + '/accounts/logout/',
ROOT_URLCONF='urls',
SESSION_ENGINE='django.contrib.sessions.backends.file',
SESSION_FILE_PATH=sessions_directory,
STATIC_URL=cfg.server_dir + '/static/',
TEMPLATE_CONTEXT_PROCESSORS=context_processors)
TEMPLATE_CONTEXT_PROCESSORS=context_processors,
TEMPLATE_DIRS=template_directories)
if not os.path.isfile(data_file):
cfg.log.info('Creating and initializing data file')
django.core.management.call_command('syncdb', interactive=False)
os.chmod(data_file, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
def main():
@ -160,8 +177,6 @@ def main():
module_loader.load_modules()
cfg.users = plugin_mount.UserStoreModule.get_plugins()[0]
setup_server()
cherrypy.engine.start()

View File

@ -1,54 +0,0 @@
import cfg
class PluginMount(type):
"""See http://martyalchin.com/2008/jan/10/simple-plugin-framework/ for documentation"""
def __init__(cls, name, bases, attrs):
if not hasattr(cls, 'plugins'):
cls.plugins = []
else:
cls.plugins.append(cls)
def init_plugins(cls, *args, **kwargs):
try:
cls.plugins = sorted(cls.plugins, key=lambda x: x.order, reverse=False)
except AttributeError:
pass
return [p(*args, **kwargs) for p in cls.plugins]
def get_plugins(cls, *args, **kwargs):
return cls.init_plugins(*args, **kwargs)
class MultiplePluginViolation(Exception):
"""Multiple plugins found for a type where only one is expected"""
pass
class PluginMountSingular(PluginMount):
"""Plugin mounter that allows only one plugin of this meta type"""
def __init__(cls, name, bases, attrs):
if not hasattr(cls, 'plugins'):
cls.plugins = []
else:
if len(cls.plugins) > 0:
raise MultiplePluginViolation
cls.plugins.append(cls)
class UserStoreModule(object):
"""
Mount Point for plugins that will manage the user backend storage,
where we keep a hash for each user.
Plugins implementing this reference should provide the following
methods, as described in the doc strings of the default
user_store.py: get, get_all, set, exists, remove, attr, expert.
See source code for doc strings.
This is designed as a plugin so mutiple types of user store can be
supported. But the project is moving towards LDAP for
compatibility with third party software. A future version of
Plinth is likely to require LDAP.
"""
# Singular because we can only use one user store at a time
__metaclass__ = PluginMountSingular

View File

@ -82,18 +82,18 @@
{% endfor %}
</ul>
{% if username %}
{% if user.is_authenticated %}
<p class="navbar-text pull-right">
<i class="icon-user icon-white nav-icon"></i>
Logged in as <a href="{{ username }}">{{ username }}</a>.
<a href="{{ basehref }}/auth/logout" title="Log out">
Logged in as <a href="{{ user.username }}">{{ user.username }}</a>.
<a href="{{ basehref }}/accounts/logout" title="Log out">
Log out</a>.
</p>
{% else %}
<p class="navbar-text pull-right">
Not logged in.
<i class="icon-user icon-white nav-icon"></i>
<a href="{{ basehref }}/auth/login" title="Log in">
<a href="{{ basehref }}/accounts/login" title="Log in">
Log in</a>.
</p>
{% endif %}

View File

@ -22,8 +22,6 @@
{% block main_block %}
{% include 'messages.html' %}
<form class="form" method="post">
{% csrf_token %}

34
templates/login.html Normal file
View File

@ -0,0 +1,34 @@
{% extends "base.html" %}
{% comment %}
#
# This file is part of Plinth.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
{% endcomment %}
{% load bootstrap %}
{% block main_block %}
<form class="form" method="post">
{% csrf_token %}
{{ form|bootstrap }}
<input type="submit" class="btn-primary" value="Login" />
<input type="hidden" name="next" value="{{ next }}" />
</form>
{% endblock %}

14
util.py
View File

@ -1,9 +1,4 @@
import os
import sys
import cfg
import sqlite3
from filedict import FileDict
def mkdir(newdir):
@ -34,12 +29,3 @@ def slurp(filespec):
def unslurp(filespec, msg):
with open(filespec, 'w') as x:
x.write(msg)
def filedict_con(filespec=None, table='dict'):
"""TODO: better error handling in filedict_con"""
try:
return FileDict(connection=sqlite3.connect(filespec), table=table)
except IOError as (errno, strerror):
cfg.log.critical("I/O error({0}): {1}".format(errno, strerror))
sys.exit(-1)

View File

@ -20,8 +20,6 @@ Main Plinth views
"""
from django.http.response import HttpResponseRedirect
import os
import stat
import cfg
from withsqlite.withsqlite import sqlite_db
@ -32,10 +30,6 @@ def index(request):
# TODO: Move firstboot handling to firstboot module somehow
with sqlite_db(cfg.store_file, table='firstboot') as database:
if not 'state' in database:
# If we created a new user db, make sure it can't be read by
# everyone
userdb_fname = '{}.sqlite3'.format(cfg.user_db)
os.chmod(userdb_fname, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
# Permanent redirect causes the browser to cache the redirect,
# preventing the user from navigating to /plinth until the
# browser is restarted.
@ -46,7 +40,7 @@ def index(request):
return HttpResponseRedirect(
cfg.server_dir + '/firstboot/state%d' % database['state'])
if request.session.get(cfg.session_key, None):
if request.user.is_authenticated():
return HttpResponseRedirect(cfg.server_dir + '/apps')
return HttpResponseRedirect(cfg.server_dir + '/help/about')