mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-04-15 09:51:21 +00:00
commit
9b1f2c6f0d
2
.gitignore
vendored
2
.gitignore
vendored
@ -24,7 +24,7 @@ TODO
|
||||
\#*
|
||||
.#*
|
||||
*~
|
||||
data/users.sqlite3
|
||||
data/plinth.sqlite3
|
||||
predepend
|
||||
build/
|
||||
*.pid
|
||||
|
||||
6
COPYING
6
COPYING
@ -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
|
||||
|
||||
5
LICENSES
5
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 :: -
|
||||
|
||||
2
cfg.py
2
cfg.py
@ -19,7 +19,7 @@ host = None
|
||||
port = None
|
||||
debug = False
|
||||
no_daemon = False
|
||||
session_key = '_username'
|
||||
server_dir = ''
|
||||
|
||||
main_menu = Menu()
|
||||
|
||||
|
||||
152
filedict.py
152
filedict.py
@ -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
|
||||
@ -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)
|
||||
|
||||
|
||||
17
model.py
17
model.py
@ -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
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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():
|
||||
|
||||
@ -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])
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
@ -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']
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 + '/')
|
||||
@ -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})
|
||||
)
|
||||
|
||||
@ -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)
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
@ -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():
|
||||
|
||||
@ -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():
|
||||
|
||||
@ -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():
|
||||
|
||||
@ -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') %
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
33
plinth.py
33
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()
|
||||
|
||||
@ -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
|
||||
@ -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 %}
|
||||
|
||||
@ -22,8 +22,6 @@
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
{% include 'messages.html' %}
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
|
||||
34
templates/login.html
Normal file
34
templates/login.html
Normal 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
14
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)
|
||||
|
||||
8
views.py
8
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')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user