diff --git a/.gitignore b/.gitignore index 1cbb5d668..ba9c727d6 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,7 @@ TODO \#* .#* *~ -data/users.sqlite3 +data/plinth.sqlite3 predepend build/ *.pid diff --git a/COPYING b/COPYING index 694d4547f..dc63b35dc 100644 --- a/COPYING +++ b/COPYING @@ -1,4 +1,4 @@ -# License to Copy Plinth +# License to Copy Plinth Plinth is Copyright 2011-2013 James Vasile (). 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 diff --git a/LICENSES b/LICENSES index 24fec33b1..06d7d8122 100644 --- a/LICENSES +++ b/LICENSES @@ -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 :: - diff --git a/cfg.py b/cfg.py index bab92ead3..c322b404e 100644 --- a/cfg.py +++ b/cfg.py @@ -19,7 +19,7 @@ host = None port = None debug = False no_daemon = False -session_key = '_username' +server_dir = '' main_menu = Menu() diff --git a/filedict.py b/filedict.py deleted file mode 100644 index 955e0e09f..000000000 --- a/filedict.py +++ /dev/null @@ -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 diff --git a/logger.py b/logger.py index 581255ebe..a1f8393f9 100644 --- a/logger.py +++ b/logger.py @@ -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) diff --git a/model.py b/model.py deleted file mode 100644 index f92952221..000000000 --- a/model.py +++ /dev/null @@ -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 diff --git a/modules/config/config.py b/modules/config/config.py index 81f1688a2..7095414cf 100644 --- a/modules/config/config.py +++ b/modules/config/config.py @@ -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 diff --git a/modules/diagnostics/diagnostics.py b/modules/diagnostics/diagnostics.py index 2639f969c..854bdf2ed 100644 --- a/modules/diagnostics/diagnostics.py +++ b/modules/diagnostics/diagnostics.py @@ -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(): diff --git a/modules/expert_mode/expert_mode.py b/modules/expert_mode/expert_mode.py index e4401d7bd..0c213625f 100644 --- a/modules/expert_mode/expert_mode.py +++ b/modules/expert_mode/expert_mode.py @@ -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]) diff --git a/modules/firewall/firewall.py b/modules/firewall/firewall.py index ae0c61d0f..8de9f6faf 100644 --- a/modules/firewall/firewall.py +++ b/modules/firewall/firewall.py @@ -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 diff --git a/modules/lib/__init__.py b/modules/lib/__init__.py index 3b0136b8c..61826142a 100644 --- a/modules/lib/__init__.py +++ b/modules/lib/__init__.py @@ -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'] diff --git a/modules/lib/auth.py b/modules/lib/auth.py index 5625511e0..0ff7bb82b 100644 --- a/modules/lib/auth.py +++ b/modules/lib/auth.py @@ -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 diff --git a/modules/lib/auth_page.py b/modules/lib/auth_page.py deleted file mode 100644 index 750ec5809..000000000 --- a/modules/lib/auth_page.py +++ /dev/null @@ -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 + '/') diff --git a/modules/lib/urls.py b/modules/lib/urls.py index 7a0cac762..20f497f35 100644 --- a/modules/lib/urls.py +++ b/modules/lib/urls.py @@ -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}) ) diff --git a/modules/lib/user_store.py b/modules/lib/user_store.py deleted file mode 100644 index c3622ede7..000000000 --- a/modules/lib/user_store.py +++ /dev/null @@ -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) diff --git a/modules/owncloud/owncloud.py b/modules/owncloud/owncloud.py index 153c13e2a..1507569b6 100644 --- a/modules/owncloud/owncloud.py +++ b/modules/owncloud/owncloud.py @@ -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 diff --git a/modules/packages/packages.py b/modules/packages/packages.py index 8776da9da..cbae9198a 100644 --- a/modules/packages/packages.py +++ b/modules/packages/packages.py @@ -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(): diff --git a/modules/pagekite/pagekite.py b/modules/pagekite/pagekite.py index 0f1329973..506f98432 100644 --- a/modules/pagekite/pagekite.py +++ b/modules/pagekite/pagekite.py @@ -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(): diff --git a/modules/tor/tor.py b/modules/tor/tor.py index 1438955de..ee3cdfdb9 100644 --- a/modules/tor/tor.py +++ b/modules/tor/tor.py @@ -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(): diff --git a/modules/users/users.py b/modules/users/users.py index b3cee4154..a3c1b056e 100644 --- a/modules/users/users.py +++ b/modules/users/users.py @@ -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') % diff --git a/modules/xmpp/xmpp.py b/modules/xmpp/xmpp.py index c03666e28..5803b2382 100644 --- a/modules/xmpp/xmpp.py +++ b/modules/xmpp/xmpp.py @@ -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 diff --git a/plinth.py b/plinth.py index ca1e85642..fb7e4e98f 100755 --- a/plinth.py +++ b/plinth.py @@ -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() diff --git a/plugin_mount.py b/plugin_mount.py deleted file mode 100644 index 09d95d09d..000000000 --- a/plugin_mount.py +++ /dev/null @@ -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 diff --git a/templates/base.html b/templates/base.html index eeebc7d80..9dfab0814 100644 --- a/templates/base.html +++ b/templates/base.html @@ -82,18 +82,18 @@ {% endfor %} - {% if username %} + {% if user.is_authenticated %} {% else %} {% endif %} diff --git a/templates/form.html b/templates/form.html index be4f8e98f..5970c1919 100644 --- a/templates/form.html +++ b/templates/form.html @@ -22,8 +22,6 @@ {% block main_block %} - {% include 'messages.html' %} -
{% csrf_token %} diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 000000000..bcf97b218 --- /dev/null +++ b/templates/login.html @@ -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 . +# +{% endcomment %} + +{% load bootstrap %} + +{% block main_block %} + + + {% csrf_token %} + + {{ form|bootstrap }} + + + + + +{% endblock %} diff --git a/util.py b/util.py index 1841700cc..f922fa51f 100644 --- a/util.py +++ b/util.py @@ -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) diff --git a/views.py b/views.py index 140962f5c..77e69d059 100644 --- a/views.py +++ b/views.py @@ -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')