mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-05-20 10:34:30 +00:00
commit
d0e0285892
6
.gitignore
vendored
6
.gitignore
vendored
@ -20,3 +20,9 @@ templates/*.py
|
|||||||
TODO
|
TODO
|
||||||
\#*
|
\#*
|
||||||
.#*
|
.#*
|
||||||
|
cfg.py
|
||||||
|
cherrypy.config
|
||||||
|
data/users.sqlite3
|
||||||
|
predepend
|
||||||
|
build/
|
||||||
|
*.pid
|
||||||
25
INSTALL
25
INSTALL
@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
## Installing Plinth
|
## Installing Plinth
|
||||||
|
|
||||||
Install the python-cheetah package and pandoc:
|
Install the python-cheetah package, pandoc, python-augeas, and
|
||||||
|
bjsonrpc:
|
||||||
apt-get install python-cheetah pandoc
|
|
||||||
|
|
||||||
|
apt-get install python-cheetah pandoc python-augeas python-bjsonrpc
|
||||||
|
|
||||||
Install the python-simplejson
|
Install the python-simplejson
|
||||||
|
|
||||||
@ -13,9 +13,16 @@ apt-get install python-simplejson
|
|||||||
|
|
||||||
|
|
||||||
Unzip and untar the source into a directory. Change to the directory
|
Unzip and untar the source into a directory. Change to the directory
|
||||||
containing the program. Do `make` and then run `./plinth.py` and
|
containing the program. Run:
|
||||||
point your web browser at `localhost:8000`. The default username is
|
|
||||||
"admin" and the default password is "secret".
|
$ make
|
||||||
|
|
||||||
|
To start Plinth, run:
|
||||||
|
|
||||||
|
$ ./start.sh
|
||||||
|
|
||||||
|
and point your web browser at `localhost:8000`. The default username is "admin"
|
||||||
|
and the default password is "secret".
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
@ -27,6 +34,10 @@ point your web browser at `localhost:8000`. The default username is
|
|||||||
|
|
||||||
* *GNU Make* is used to build the templates and such.
|
* *GNU Make* is used to build the templates and such.
|
||||||
|
|
||||||
|
* bjsonrpc - used for configuration management layer
|
||||||
|
|
||||||
|
* python-augeas and augeas - used for configuration management
|
||||||
|
|
||||||
The documentation has some dependencies too.
|
The documentation has some dependencies too.
|
||||||
|
|
||||||
* *Markdown* is used to format and style docs.
|
* *Markdown* is used to format and style docs.
|
||||||
@ -42,5 +53,3 @@ The documentation has some dependencies too.
|
|||||||
Documentation has been collected into a pdf that can be built using
|
Documentation has been collected into a pdf that can be built using
|
||||||
`make doc`. It also gets built into smaller files and other formats,
|
`make doc`. It also gets built into smaller files and other formats,
|
||||||
including one suitable for install as a man page.
|
including one suitable for install as a man page.
|
||||||
|
|
||||||
<a name="installing_systemwide" />
|
|
||||||
|
|||||||
18
Makefile
18
Makefile
@ -1,5 +1,6 @@
|
|||||||
#SHELL := /bin/bash
|
#SHELL := /bin/bash
|
||||||
MAKE=make
|
MAKE=make
|
||||||
|
BUILD_DIR = build
|
||||||
|
|
||||||
#TODO: add install target
|
#TODO: add install target
|
||||||
|
|
||||||
@ -9,9 +10,22 @@ COMPRESSED_CSS := $(patsubst %.css,%.tiny.css,$(CSS))
|
|||||||
PWD=`pwd`
|
PWD=`pwd`
|
||||||
|
|
||||||
## Catch-all tagets
|
## Catch-all tagets
|
||||||
default: cfg cherrypy.config dirs template css docs dbs
|
default: predepend cfg cherrypy.config dirs template css docs dbs $(BUILD_DIR)/exmachina #$(BUILD_DIR)/bjsonrpc
|
||||||
all: default
|
all: default
|
||||||
|
|
||||||
|
build:
|
||||||
|
mkdir -p $(BUILD_DIR)
|
||||||
|
|
||||||
|
predepend:
|
||||||
|
sudo sh -c "apt-get install augeas-tools python-bjsonrpc python-augeas python-simplejson pandoc python-cheetah"
|
||||||
|
touch predepend
|
||||||
|
|
||||||
|
$(BUILD_DIR)/exmachina: build
|
||||||
|
git clone git://github.com/tomgalloway/exmachina $(BUILD_DIR)/exmachina
|
||||||
|
|
||||||
|
$(BUILD_DIR)/bjsonrpc: build
|
||||||
|
git clone git://github.com/deavid/bjsonrpc.git $(BUILD_DIR)/bjsonrpc
|
||||||
|
|
||||||
dbs: data/users.sqlite3
|
dbs: data/users.sqlite3
|
||||||
|
|
||||||
data/users.sqlite3: data/users.sqlite3.distrib
|
data/users.sqlite3: data/users.sqlite3.distrib
|
||||||
@ -69,3 +83,5 @@ clean:
|
|||||||
@find . -name "*.bak" -exec rm {} \;
|
@find . -name "*.bak" -exec rm {} \;
|
||||||
@$(MAKE) -s -C doc clean
|
@$(MAKE) -s -C doc clean
|
||||||
@$(MAKE) -s -C templates clean
|
@$(MAKE) -s -C templates clean
|
||||||
|
rm -rf $(BUILD_DIR)
|
||||||
|
rm -f predepend
|
||||||
|
|||||||
12
NOTES
12
NOTES
@ -2,6 +2,18 @@
|
|||||||
%
|
%
|
||||||
% February 2012
|
% February 2012
|
||||||
|
|
||||||
|
# Edits by bnewbold
|
||||||
|
|
||||||
|
## 2012-07-12 "exmachina" configuration management layer
|
||||||
|
|
||||||
|
- this new code is very ugly and in the "just make it work" style
|
||||||
|
- add exmachina code and test code
|
||||||
|
- modify plinth.py to listen for shared secret on stdin at start
|
||||||
|
(if appropriate flag is set) and try to connect to exmachina daemon
|
||||||
|
- use exmachina to read and set /etc/hostname as a demo
|
||||||
|
- update plinth init.d script to start exmachina and share keys
|
||||||
|
- update docs with new deps and run instructions
|
||||||
|
|
||||||
# Edits by seandiggity
|
# Edits by seandiggity
|
||||||
|
|
||||||
## 2012-02-27 new theme based upon bootstrap
|
## 2012-02-27 new theme based upon bootstrap
|
||||||
|
|||||||
3
README
3
README
@ -50,4 +50,5 @@ interface will overwrite those changes at first opportunity. This
|
|||||||
interface is not a tool for super admins facing complex scenarios. It
|
interface is not a tool for super admins facing complex scenarios. It
|
||||||
is for home users to do a wide variety of basic tasks.
|
is for home users to do a wide variety of basic tasks.
|
||||||
|
|
||||||
|
See comments in exmachina/exmachina.py for more details about the configuration
|
||||||
|
management process seperation scheme.
|
||||||
|
|||||||
@ -8,10 +8,12 @@ user_db = os.path.join(data_dir, "users")
|
|||||||
status_log_file = os.path.join(data_dir, "status.log")
|
status_log_file = os.path.join(data_dir, "status.log")
|
||||||
access_log_file = os.path.join(data_dir, "access.log")
|
access_log_file = os.path.join(data_dir, "access.log")
|
||||||
users_dir = os.path.join(data_dir, "users")
|
users_dir = os.path.join(data_dir, "users")
|
||||||
|
pidfile = os.path.join(data_dir, "pidfile.pid")
|
||||||
|
|
||||||
product_name = "Plinth"
|
product_name = "Plinth"
|
||||||
box_name = "FreedomBox"
|
box_name = "FreedomBox"
|
||||||
|
|
||||||
|
host = "127.0.0.1"
|
||||||
port = 8000
|
port = 8000
|
||||||
|
|
||||||
## Do not edit below this line ##
|
## Do not edit below this line ##
|
||||||
|
|||||||
2
fabfile.py
vendored
2
fabfile.py
vendored
@ -138,7 +138,7 @@ def apache():
|
|||||||
@task
|
@task
|
||||||
def deps():
|
def deps():
|
||||||
"Basic plinth dependencies"
|
"Basic plinth dependencies"
|
||||||
sudo('apt-get install --no-install-recommends -y python make python-cheetah pandoc python-simplejson python-pyme')
|
sudo('apt-get install --no-install-recommends -y python make python-cheetah pandoc python-simplejson python-pyme python-augeas python-bjsonrpc')
|
||||||
|
|
||||||
@task
|
@task
|
||||||
def update():
|
def update():
|
||||||
|
|||||||
42
issues/links.org
Normal file
42
issues/links.org
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# -*- mode: org; mode: auto-fill; fill-column: 80 -*-
|
||||||
|
|
||||||
|
#+TITLE: Make Links Portable
|
||||||
|
#+OPTIONS: d:t
|
||||||
|
#+LINK_UP: ./
|
||||||
|
#+LINK_HOME: ../
|
||||||
|
|
||||||
|
* Issue
|
||||||
|
|
||||||
|
Currently, all the links in Plinth point to 127.0.0.1/(something), and that
|
||||||
|
sucks for serving Plinth on a local network, like most use cases imply.
|
||||||
|
|
||||||
|
* Fixes [0/1]
|
||||||
|
|
||||||
|
** TODO Links work when accessed from remote clients.
|
||||||
|
|
||||||
|
Investigate the following:
|
||||||
|
|
||||||
|
: grep -nHr basehref ../*
|
||||||
|
|
||||||
|
: grep -nHr 127.0 ../*
|
||||||
|
|
||||||
|
#+begin_ascii
|
||||||
|
fabfile.py:40: if env.host == "localhost" or env.host=="127.0.0.1":
|
||||||
|
fabfile.py:46: if env.host == "localhost" or env.host=="127.0.0.1":
|
||||||
|
fabfile.py:102: hidden_service_config = "HiddenServiceDir %s\nHiddenServicePort 80 127.0.0.1:%d" % (tor_dir, santiago_port)
|
||||||
|
modules/installed/santiago/santiago.py:48: hidden_service_config = "HiddenServiceDir %s\nHiddenServicePort 80 127.0.0.1:%d" % (self.tor_dir, santiago_port)
|
||||||
|
plinth.py:119: server.socket_host = '127.0.0.1'
|
||||||
|
#+end_ascii
|
||||||
|
|
||||||
|
Also, why is base_href blank in [[file:~/programs/freedombox/plinth/cfg.sample.py][cfg.sample.py]]?
|
||||||
|
|
||||||
|
* Discussion
|
||||||
|
|
||||||
|
* Metadata
|
||||||
|
:PROPERTIES:
|
||||||
|
:Status: Incomplete
|
||||||
|
:Priority: 0
|
||||||
|
:Owner: Nick Daly
|
||||||
|
:Description:
|
||||||
|
:Tags:
|
||||||
|
:END:
|
||||||
5
model.py
5
model.py
@ -1,12 +1,13 @@
|
|||||||
class User(dict):
|
class User(dict):
|
||||||
""" Every user must have keys for a username, name, password (this
|
""" Every user must have keys for a username, name, passphrase (this
|
||||||
is a md5 hash of the password), groups, and an email address. They can be
|
is a md5 hash of the password), groups, and an email address. They can be
|
||||||
blank or None, but the keys must exist. """
|
blank or None, but the keys must exist. """
|
||||||
def __init__(self, dict=None):
|
def __init__(self, dict=None):
|
||||||
for key in ['username', 'name', 'password', 'email']:
|
for key in ['username', 'name', 'passphrase', 'email']:
|
||||||
self[key] = ''
|
self[key] = ''
|
||||||
for key in ['groups']:
|
for key in ['groups']:
|
||||||
self[key] = []
|
self[key] = []
|
||||||
|
if dict:
|
||||||
for key in dict:
|
for key in dict:
|
||||||
self[key] = dict[key]
|
self[key] = dict[key]
|
||||||
|
|
||||||
|
|||||||
@ -12,10 +12,42 @@ class UserStore(UserStoreModule, sqlite_db):
|
|||||||
self.db_file = cfg.user_db
|
self.db_file = cfg.user_db
|
||||||
sqlite_db.__init__(self, self.db_file, autocommit=True)
|
sqlite_db.__init__(self, self.db_file, autocommit=True)
|
||||||
self.__enter__()
|
self.__enter__()
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self.__exit__()
|
self.__exit__(None,None,None)
|
||||||
def expert(self):
|
|
||||||
|
def expert(self, username=None):
|
||||||
|
groups = self.attr(username,"groups")
|
||||||
|
if not groups:
|
||||||
return False
|
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)
|
||||||
|
self.commit()
|
||||||
|
|
||||||
|
def get_all(self):
|
||||||
|
return self.items()
|
||||||
|
|
||||||
|
def set(self,username=None,user=None):
|
||||||
|
sqlite_db.__setitem__(self,username, user)
|
||||||
|
|
||||||
class UserStoreOld():
|
class UserStoreOld():
|
||||||
#class UserStore(UserStoreModule):
|
#class UserStore(UserStoreModule):
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import os, shutil, subprocess
|
import os, subprocess
|
||||||
from socket import gethostname
|
from socket import gethostname
|
||||||
import cherrypy
|
import cherrypy
|
||||||
import simplejson as json
|
import simplejson as json
|
||||||
@ -10,6 +10,7 @@ import cfg
|
|||||||
from forms import Form
|
from forms import Form
|
||||||
from model import User
|
from model import User
|
||||||
from util import *
|
from util import *
|
||||||
|
import platform
|
||||||
|
|
||||||
class Config(PagePlugin):
|
class Config(PagePlugin):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -41,19 +42,20 @@ def valid_hostname(name):
|
|||||||
|
|
||||||
def set_hostname(hostname):
|
def set_hostname(hostname):
|
||||||
"Sets machine hostname to hostname"
|
"Sets machine hostname to hostname"
|
||||||
cfg.log.info("Writing '%s' to /etc/hostname" % hostname)
|
cfg.log.info("Writing '%s' to /etc/hostname with exmachina" % hostname)
|
||||||
unslurp("/etc/hostname", hostname+"\n")
|
|
||||||
try:
|
|
||||||
retcode = subprocess.call("/etc/init.d/hostname.sh start", shell=True)
|
|
||||||
if retcode < 0:
|
|
||||||
cfg.log.error("Hostname restart terminated by signal: return code is %s" % retcode)
|
|
||||||
else:
|
|
||||||
cfg.log.debug("Hostname restart returned %s" % retcode)
|
|
||||||
except OSError, e:
|
|
||||||
raise cherrypy.HTTPError(500, "Hostname restart failed: %s" % e)
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
cfg.exmachina.augeas.set("/files/etc/hostname/*", hostname)
|
||||||
|
cfg.exmachina.augeas.save()
|
||||||
|
# don't persist/cache change unless it was saved successfuly
|
||||||
sys_store = filedict_con(cfg.store_file, 'sys')
|
sys_store = filedict_con(cfg.store_file, 'sys')
|
||||||
sys_store['hostname'] = hostname
|
sys_store['hostname'] = hostname
|
||||||
|
if platform.linux_distribution()[0]=="Ubuntu" :
|
||||||
|
cfg.exmachina.service.start("hostname")
|
||||||
|
else:
|
||||||
|
cfg.exmachina.initd.restart("hostname.sh") # is hostname.sh debian-only?
|
||||||
|
except OSError, e:
|
||||||
|
raise cherrypy.HTTPError(500, "Hostname restart failed: %s" % e)
|
||||||
|
|
||||||
class general(FormPlugin, PagePlugin):
|
class general(FormPlugin, PagePlugin):
|
||||||
url = ["/sys/config"]
|
url = ["/sys/config"]
|
||||||
@ -72,8 +74,11 @@ class general(FormPlugin, PagePlugin):
|
|||||||
|
|
||||||
def main(self, message='', **kwargs):
|
def main(self, message='', **kwargs):
|
||||||
sys_store = filedict_con(cfg.store_file, 'sys')
|
sys_store = filedict_con(cfg.store_file, 'sys')
|
||||||
|
hostname = cfg.exmachina.augeas.get("/files/etc/hostname/*")
|
||||||
|
# this layer of persisting configuration in sys_store could/should be
|
||||||
|
# removed -BLN
|
||||||
defaults = {'time_zone': "slurp('/etc/timezone').rstrip()",
|
defaults = {'time_zone': "slurp('/etc/timezone').rstrip()",
|
||||||
'hostname': "gethostname()",
|
'hostname': "hostname",
|
||||||
}
|
}
|
||||||
for k,c in defaults.items():
|
for k,c in defaults.items():
|
||||||
if not k in kwargs:
|
if not k in kwargs:
|
||||||
@ -81,6 +86,8 @@ class general(FormPlugin, PagePlugin):
|
|||||||
kwargs[k] = sys_store[k]
|
kwargs[k] = sys_store[k]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
exec("if not '%(k)s' in kwargs: sys_store['%(k)s'] = kwargs['%(k)s'] = %(c)s" % {'k':k, 'c':c})
|
exec("if not '%(k)s' in kwargs: sys_store['%(k)s'] = kwargs['%(k)s'] = %(c)s" % {'k':k, 'c':c})
|
||||||
|
# over-ride the sys_store cached value
|
||||||
|
kwargs['hostname'] = hostname
|
||||||
|
|
||||||
## Get the list of supported timezones and the index in that list of the current one
|
## Get the list of supported timezones and the index in that list of the current one
|
||||||
module_file = __file__
|
module_file = __file__
|
||||||
@ -120,7 +127,8 @@ class general(FormPlugin, PagePlugin):
|
|||||||
old_val = sys_store['hostname']
|
old_val = sys_store['hostname']
|
||||||
try:
|
try:
|
||||||
set_hostname(hostname)
|
set_hostname(hostname)
|
||||||
except:
|
except Exception, e:
|
||||||
|
cfg.log.error(e)
|
||||||
cfg.log.info("Trying to restore old hostname value.")
|
cfg.log.info("Trying to restore old hostname value.")
|
||||||
set_hostname(old_val)
|
set_hostname(old_val)
|
||||||
raise
|
raise
|
||||||
@ -128,8 +136,8 @@ class general(FormPlugin, PagePlugin):
|
|||||||
message += msg
|
message += msg
|
||||||
if time_zone != sys_store['time_zone']:
|
if time_zone != sys_store['time_zone']:
|
||||||
src = os.path.join("/usr/share/zoneinfo", time_zone)
|
src = os.path.join("/usr/share/zoneinfo", time_zone)
|
||||||
cfg.log.info("Copying %s to /etc/localtime" % src)
|
cfg.log.info("Setting timezone to %s" % time_zone)
|
||||||
shutil.copy(src, "/etc/localtime")
|
cfg.exmachina.misc.set_timezone(time_zone)
|
||||||
sys_store['time_zone'] = time_zone
|
sys_store['time_zone'] = time_zone
|
||||||
return message or "Settings updated."
|
return message or "Settings updated."
|
||||||
|
|
||||||
|
|||||||
@ -5,22 +5,23 @@ from plugin_mount import PagePlugin, FormPlugin
|
|||||||
import cfg
|
import cfg
|
||||||
from forms import Form
|
from forms import Form
|
||||||
from util import *
|
from util import *
|
||||||
|
from model import User
|
||||||
|
|
||||||
class users(PagePlugin):
|
class users(PagePlugin):
|
||||||
order = 20 # order of running init in PagePlugins
|
order = 20 # order of running init in PagePlugins
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
PagePlugin.__init__(self, *args, **kwargs)
|
PagePlugin.__init__(self, *args, **kwargs)
|
||||||
self.register_page("sys.users")
|
self.register_page("sys.users")
|
||||||
|
self.register_page("sys.users.add")
|
||||||
|
self.register_page("sys.users.edit")
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@require()
|
@require()
|
||||||
def index(self):
|
def index(self):
|
||||||
parts = self.forms('/sys/config')
|
return self.fill_template(title="Manage Users and Groups", sidebar_right="""<strong><a href="/sys/users/add">Add User</a></strong><br/><strong><a href="/sys/users/edit">Edit Users</a></strong>""")
|
||||||
parts['title']=_("Manage Users and Groups")
|
|
||||||
return self.fill_template(**parts)
|
|
||||||
|
|
||||||
class add(FormPlugin, PagePlugin):
|
class add(FormPlugin, PagePlugin):
|
||||||
url = ["/sys/users"]
|
url = ["/sys/users/add"]
|
||||||
order = 30
|
order = 30
|
||||||
|
|
||||||
sidebar_left = ''
|
sidebar_left = ''
|
||||||
@ -40,33 +41,35 @@ class add(FormPlugin, PagePlugin):
|
|||||||
form.text_input(_("Username"), name="username", value=username)
|
form.text_input(_("Username"), name="username", value=username)
|
||||||
form.text_input(_("Full name"), name="name", value=name)
|
form.text_input(_("Full name"), name="name", value=name)
|
||||||
form.text_input(_("Email"), name="email", value=email)
|
form.text_input(_("Email"), name="email", value=email)
|
||||||
form.text_input(_("Password"), name="password")
|
form.text_input(_("Password"), name="password", type="password")
|
||||||
form.text_input(name="md5_password", type="hidden")
|
form.text_input(name="md5_password", type="hidden")
|
||||||
form.submit(label=_("Create User"), name="create")
|
form.submit(label=_("Create User"), name="create")
|
||||||
return form.render()
|
return form.render()
|
||||||
|
|
||||||
def process_form(self, username=None, name=None, email=None, md5_password=None, **kwargs):
|
def process_form(self, username=None, name=None, email=None, md5_password=None, **kwargs):
|
||||||
msg = ''
|
msg = Message()
|
||||||
|
|
||||||
if not username: msg = add_message(msg, _("Must specify a username!"))
|
if not username: msg.add = _("Must specify a username!")
|
||||||
if not md5_password: msg = add_message(msg, _("Must specify a password!"))
|
if not md5_password: msg.add = _("Must specify a password!")
|
||||||
|
|
||||||
if username in cfg.users:
|
if username in cfg.users.get_all():
|
||||||
msg = add_message(msg, _("User already exists!"))
|
msg.add = _("User already exists!")
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
cfg.users[username]= User(dict={'username':username, 'name':name, 'email':email, 'password':md5_password})
|
di = {'username':username, 'name':name, 'email':email, 'passphrase':md5_password}
|
||||||
|
new_user = User(di)
|
||||||
|
cfg.users.set(username,new_user)
|
||||||
except:
|
except:
|
||||||
msg = add_message(msg, _("Error storing user!"))
|
msg.add = _("Error storing user!")
|
||||||
|
|
||||||
if not msg:
|
if not msg:
|
||||||
msg = add_message(msg, "%s saved." % username)
|
msg.add = _("%s saved." % username)
|
||||||
|
cfg.log(msg.text)
|
||||||
main = self.make_form(username, name, email, message=msg)
|
main = self.main(username, name, email, msg=msg.text)
|
||||||
return self.fill_template(title="", main=main, sidebar_left=self.sidebar_left, sidebar_right=self.sidebar_right)
|
return self.fill_template(title="Manage Users and Groups", main=main, sidebar_left=self.sidebar_left, sidebar_right=self.sidebar_right)
|
||||||
|
|
||||||
class edit(FormPlugin, PagePlugin):
|
class edit(FormPlugin, PagePlugin):
|
||||||
url = ["/sys/users"]
|
url = ["/sys/users/edit"]
|
||||||
order = 35
|
order = 35
|
||||||
|
|
||||||
sidebar_left = ''
|
sidebar_left = ''
|
||||||
@ -77,14 +80,15 @@ class edit(FormPlugin, PagePlugin):
|
|||||||
system.</p><p>Deleting users is permanent!</p>""" % (cfg.product_name, cfg.box_name))
|
system.</p><p>Deleting users is permanent!</p>""" % (cfg.product_name, cfg.box_name))
|
||||||
|
|
||||||
def main(self, msg=''):
|
def main(self, msg=''):
|
||||||
users = cfg.users.keys()
|
users = cfg.users.get_all()
|
||||||
add_form = Form(title=_("Edit or Delete User"), action="/sys/users/edit", message=msg)
|
add_form = Form(title=_("Edit or Delete User"), action="/sys/users/edit", message=msg)
|
||||||
add_form.html('<span class="indent"><strong>Delete</strong><br /></span>')
|
add_form.html('<span class="indent"><strong>Delete</strong><br /></span>')
|
||||||
for uname in sorted(users.keys()):
|
for uname in users:
|
||||||
|
user = User(uname[1])
|
||||||
add_form.html('<span class="indent"> %s ' %
|
add_form.html('<span class="indent"> %s ' %
|
||||||
add_form.get_checkbox(name=uname) +
|
add_form.get_checkbox(name=user['username']) +
|
||||||
'<a href="/sys/users/edit?username=%s">%s (%s)</a><br /></span>' %
|
'<a href="/sys/users/edit?username=%s">%s (%s)</a><br /></span>' %
|
||||||
(uname, users[uname]['name'], uname))
|
(user['username'], user['name'], user['username']))
|
||||||
add_form.submit(label=_("Delete User"), name="delete")
|
add_form.submit(label=_("Delete User"), name="delete")
|
||||||
return add_form.render()
|
return add_form.render()
|
||||||
|
|
||||||
@ -95,12 +99,12 @@ class edit(FormPlugin, PagePlugin):
|
|||||||
cfg.log.info("%s asked to delete %s" % (cherrypy.session.get(cfg.session_key), usernames))
|
cfg.log.info("%s asked to delete %s" % (cherrypy.session.get(cfg.session_key), usernames))
|
||||||
if usernames:
|
if usernames:
|
||||||
for username in usernames:
|
for username in usernames:
|
||||||
if username in cfg.users:
|
if cfg.users.exists(username):
|
||||||
try:
|
try:
|
||||||
del cfg.users[username]
|
cfg.users.remove(username)
|
||||||
msg.add(_("Deleted user %s." % username))
|
msg.add(_("Deleted user %s." % username))
|
||||||
except IOError, e:
|
except IOError, e:
|
||||||
if 'username' in cfg.users:
|
if cfg.users.exists(username):
|
||||||
m = _("Error on deletion, user %s not fully deleted: %s" % (username, e))
|
m = _("Error on deletion, user %s not fully deleted: %s" % (username, e))
|
||||||
cfg.log.error(m)
|
cfg.log.error(m)
|
||||||
msg.add(m)
|
msg.add(m)
|
||||||
@ -113,8 +117,8 @@ class edit(FormPlugin, PagePlugin):
|
|||||||
msg.add(_("User %s does not exist." % username))
|
msg.add(_("User %s does not exist." % username))
|
||||||
else:
|
else:
|
||||||
msg.add = _("Must specify at least one valid, existing user.")
|
msg.add = _("Must specify at least one valid, existing user.")
|
||||||
main = self.make_form(msg=msg.text)
|
main = self.main(msg=msg.text)
|
||||||
return self.fill_template(title="", main=main, sidebar_left=self.sidebar_left, sidebar_right=self.sidebar_right)
|
return self.fill_template(title="Manage Users and Groups", main=main, sidebar_left=self.sidebar_left, sidebar_right=self.sidebar_right)
|
||||||
|
|
||||||
sidebar_right = ''
|
sidebar_right = ''
|
||||||
u = cfg.users[kwargs['username']]
|
u = cfg.users[kwargs['username']]
|
||||||
@ -125,4 +129,4 @@ class edit(FormPlugin, PagePlugin):
|
|||||||
|
|
||||||
main = _("""<strong>Edit User '%s'</strong>""" % u['username'])
|
main = _("""<strong>Edit User '%s'</strong>""" % u['username'])
|
||||||
sidebar_right = ''
|
sidebar_right = ''
|
||||||
return self.fill_template(title="", main=main, sidebar_left=self.sidebar_left, sidebar_right=sidebar_right)
|
return self.fill_template(title="Manage Users and Groups", main=main, sidebar_left=self.sidebar_left, sidebar_right=sidebar_right)
|
||||||
|
|||||||
25
plinth.py
25
plinth.py
@ -17,6 +17,9 @@ from util import *
|
|||||||
from logger import Logger
|
from logger import Logger
|
||||||
#from modules.auth import AuthController, require, member_of, name_is
|
#from modules.auth import AuthController, require, member_of, name_is
|
||||||
|
|
||||||
|
from exmachina import ExMachinaClient
|
||||||
|
import socket
|
||||||
|
|
||||||
__version__ = "0.2.14"
|
__version__ = "0.2.14"
|
||||||
__author__ = "James Vasile"
|
__author__ = "James Vasile"
|
||||||
__copyright__ = "Copyright 2011, James Vasile"
|
__copyright__ = "Copyright 2011, James Vasile"
|
||||||
@ -71,9 +74,20 @@ def parse_arguments():
|
|||||||
parser = argparse.ArgumentParser(description='Plinth web interface for the FreedomBox.')
|
parser = argparse.ArgumentParser(description='Plinth web interface for the FreedomBox.')
|
||||||
parser.add_argument('--pidfile', default="",
|
parser.add_argument('--pidfile', default="",
|
||||||
help='specify a file in which the server may write its pid')
|
help='specify a file in which the server may write its pid')
|
||||||
|
parser.add_argument('--listen-exmachina-key', default=False, action='store_true',
|
||||||
|
help='listen for JSON-RPC shared secret key on stdin at startup')
|
||||||
args=parser.parse_args()
|
args=parser.parse_args()
|
||||||
if args.pidfile:
|
if args.pidfile:
|
||||||
cfg.pidfile = args.pidfile
|
cfg.pidfile = args.pidfile
|
||||||
|
else:
|
||||||
|
if not cfg.pidfile:
|
||||||
|
cfg.pidfile = "plinth.pid"
|
||||||
|
if args.listen_exmachina_key:
|
||||||
|
# this is where we optionally try to read in a shared secret key to
|
||||||
|
# authenticate connections to exmachina
|
||||||
|
cfg.exmachina_secret_key = sys.stdin.readline().strip()
|
||||||
|
else:
|
||||||
|
cfg.exmachina_secret_key = None
|
||||||
|
|
||||||
def setup():
|
def setup():
|
||||||
parse_arguments()
|
parse_arguments()
|
||||||
@ -85,6 +99,13 @@ def setup():
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
cfg.exmachina = ExMachinaClient(
|
||||||
|
secret_key=cfg.exmachina_secret_key or None)
|
||||||
|
except socket.error:
|
||||||
|
cfg.exmachina = None
|
||||||
|
print "couldn't connect to exmachina daemon, but continuing anyways..."
|
||||||
|
|
||||||
os.chdir(cfg.file_root)
|
os.chdir(cfg.file_root)
|
||||||
cherrypy.config.update({'error_page.404': error_page_404})
|
cherrypy.config.update({'error_page.404': error_page_404})
|
||||||
cherrypy.config.update({'error_page.500': error_page_500})
|
cherrypy.config.update({'error_page.500': error_page_500})
|
||||||
@ -103,7 +124,7 @@ def setup():
|
|||||||
server.subscribe()
|
server.subscribe()
|
||||||
|
|
||||||
# Configure default server
|
# Configure default server
|
||||||
cherrypy.config.update({'server.socket_host': '127.0.0.1',
|
cherrypy.config.update({'server.socket_host': cfg.host,
|
||||||
'server.socket_port': cfg.port,
|
'server.socket_port': cfg.port,
|
||||||
'server.thread_pool':10,
|
'server.thread_pool':10,
|
||||||
'tools.staticdir.root': cfg.file_root,
|
'tools.staticdir.root': cfg.file_root,
|
||||||
@ -128,7 +149,7 @@ def setup():
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
setup()
|
setup()
|
||||||
print "localhost %d" % cfg.port
|
print "%s %d" % (cfg.host, cfg.port)
|
||||||
|
|
||||||
cherrypy.engine.start()
|
cherrypy.engine.start()
|
||||||
cherrypy.engine.block()
|
cherrypy.engine.block()
|
||||||
|
|||||||
@ -12,23 +12,53 @@
|
|||||||
|
|
||||||
# This file is /etc/init.d/plinth
|
# This file is /etc/init.d/plinth
|
||||||
DAEMON=/usr/local/bin/plinth.py
|
DAEMON=/usr/local/bin/plinth.py
|
||||||
|
EXMACHINA_DAEMON=/usr/local/bin/exmachina.py
|
||||||
PID_FILE=/var/run/plinth.pid
|
PID_FILE=/var/run/plinth.pid
|
||||||
|
EXMACHINA_PID_FILE=/var/run/exmachina.pid
|
||||||
|
|
||||||
|
PLINTH_USER=www-data
|
||||||
|
PLINTH_GROUP=www-data
|
||||||
|
|
||||||
|
test -x $DAEMON || exit 0
|
||||||
|
test -x $EXMACHINA_DAEMON || exit 0
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
. /lib/lsb/init-functions
|
||||||
|
|
||||||
start_plinth (){
|
start_plinth (){
|
||||||
if [ -f $PID_FILE ]; then
|
if [ -f $PID_FILE ]; then
|
||||||
echo Already running with a pid of `cat $PID_FILE`.
|
echo Already running with a pid of `cat $PID_FILE`.
|
||||||
else
|
else
|
||||||
$DAEMON --pidfile=$PID_FILE
|
if [ -f $EXMACHINA_PID_FILE ]; then
|
||||||
|
echo exmachina was already running with a pid of `cat $EXMACHINA_PID_FILE`.
|
||||||
|
kill -15 `cat $EXMACHINA_PID_FILE`
|
||||||
|
rm -rf $EXMACHINA_PID_FILE
|
||||||
|
fi
|
||||||
|
SHAREDKEY=`$EXMACHINA_DAEMON --random-key`
|
||||||
|
touch $PID_FILE
|
||||||
|
chown $PLINTH_USER:$PLINTH_GROUP $PID_FILE
|
||||||
|
echo $SHAREDKEY | $EXMACHINA_DAEMON --pidfile=$EXMACHINA_PID_FILE --group=$PLINTH_GROUP || rm $PID_FILE
|
||||||
|
sleep 0.5
|
||||||
|
echo $SHAREDKEY | sudo -u $PLINTH_USER -g $PLINTH_GROUP $DAEMON --pidfile=$PID_FILE
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
stop_plinth () {
|
stop_plinth () {
|
||||||
if [ -f $PID_FILE ]; then
|
if [ -f $PID_FILE ]; then
|
||||||
kill -15 `cat $PID_FILE`
|
kill -15 `cat $PID_FILE` || true
|
||||||
rm -rf $PID_FILE
|
rm -rf $PID_FILE
|
||||||
|
echo "killed plinth"
|
||||||
else
|
else
|
||||||
echo "No pid file at $PID_FILE suggests plinth is not running."
|
echo "No pid file at $PID_FILE suggests plinth is not running."
|
||||||
fi
|
fi
|
||||||
|
if [ -f $EXMACHINA_PID_FILE ]; then
|
||||||
|
kill -15 `cat $EXMACHINA_PID_FILE` || true
|
||||||
|
rm -rf $EXMACHINA_PID_FILE
|
||||||
|
echo "killed exmachina"
|
||||||
|
else
|
||||||
|
echo "No pid file at $EXMACHINA_PID_FILE suggests exmachina is not running."
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
test -x $DAEMON || exit 0
|
test -x $DAEMON || exit 0
|
||||||
@ -45,5 +75,12 @@ case "$1" in
|
|||||||
$0 stop
|
$0 stop
|
||||||
$0 start
|
$0 start
|
||||||
;;
|
;;
|
||||||
|
status)
|
||||||
|
status_of_proc -p $PID_FILE "$DAEMON" plinth && exit 0 || exit $?
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $NAME {start|stop|restart|status}" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
|
||||||
esac
|
esac
|
||||||
|
|||||||
10
start.sh
Executable file
10
start.sh
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
#! /bin/sh
|
||||||
|
|
||||||
|
PYTHONPATH=build/exmachina:$PYTHONPATH
|
||||||
|
|
||||||
|
export PYTHONPATH
|
||||||
|
|
||||||
|
sudo killall exmachina.py
|
||||||
|
sudo build/exmachina/exmachina.py -v &
|
||||||
|
python plinth.py
|
||||||
|
sudo killall exmachina.py
|
||||||
9
test.sh
Executable file
9
test.sh
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
#! /bin/sh
|
||||||
|
|
||||||
|
PYTHONPATH=build/exmachina:$PYTHONPATH
|
||||||
|
PYTHONPATH=modules/installed/lib:$PYTHONPATH
|
||||||
|
PYTHONPATH=vendor:$PYTHONPATH
|
||||||
|
|
||||||
|
export PYTHONPATH
|
||||||
|
|
||||||
|
python tests/test_user_store.py
|
||||||
86
tests/test_user_store.py
Normal file
86
tests/test_user_store.py
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
#! /usr/bin/env python
|
||||||
|
# -*- mode: python; mode: auto-fill; fill-column: 80 -*-
|
||||||
|
|
||||||
|
import user_store
|
||||||
|
from logger import Logger
|
||||||
|
import cfg
|
||||||
|
import unittest
|
||||||
|
import cherrypy
|
||||||
|
import plugin_mount
|
||||||
|
import os
|
||||||
|
from model import User
|
||||||
|
cfg.log = Logger()
|
||||||
|
|
||||||
|
cherrypy.log.access_file = None
|
||||||
|
|
||||||
|
class UserStore(unittest.TestCase):
|
||||||
|
"""Test each function of user_store to confirm they work as expected"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
cfg.user_db = os.path.join(cfg.file_root, "tests/testdata/users");
|
||||||
|
self.userstore = plugin_mount.UserStoreModule.get_plugins()[0]
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
for user in self.userstore.get_all():
|
||||||
|
self.userstore.remove(user[0])
|
||||||
|
self.userstore.close()
|
||||||
|
|
||||||
|
def test_user_does_not_exist(self):
|
||||||
|
self.assertEqual(self.userstore.exists("notausername"),False)
|
||||||
|
|
||||||
|
def test_user_does_exist(self):
|
||||||
|
self.add_user("isausername", False)
|
||||||
|
self.assertEqual(self.userstore.exists("isausername"),True)
|
||||||
|
|
||||||
|
def test_add_user(self):
|
||||||
|
self.assertEqual(len(self.userstore.items()),0)
|
||||||
|
self.add_user("test_user", False)
|
||||||
|
self.assertEqual(len(self.userstore.items()),1)
|
||||||
|
|
||||||
|
def test_user_is_in_expert_group(self):
|
||||||
|
self.add_user("test_user", True)
|
||||||
|
self.assertEqual(self.userstore.expert("test_user"),True)
|
||||||
|
|
||||||
|
def test_user_is_not_in_expert_group(self):
|
||||||
|
self.add_user("test_user", False)
|
||||||
|
self.assertEqual(self.userstore.expert("test_user"),False)
|
||||||
|
|
||||||
|
def test_user_removal(self):
|
||||||
|
self.assertEqual(len(self.userstore.items()),0)
|
||||||
|
self.add_user("test_user", False)
|
||||||
|
self.assertEqual(len(self.userstore.items()),1)
|
||||||
|
self.userstore.remove("test_user")
|
||||||
|
self.assertEqual(len(self.userstore.items()),0)
|
||||||
|
|
||||||
|
def test_get_user_email_attribute(self):
|
||||||
|
self.add_user("test_user", False,"test@home")
|
||||||
|
self.assertEqual(self.userstore.attr("test_user","email"),"test@home")
|
||||||
|
|
||||||
|
def test_get_user(self):
|
||||||
|
test_user = self.add_user("test_user", False)
|
||||||
|
self.assertEqual(self.userstore.get("test_user"),test_user)
|
||||||
|
|
||||||
|
def test_get_all_users(self):
|
||||||
|
self.add_user("test_user1", False)
|
||||||
|
self.add_user("test_user2", False)
|
||||||
|
self.assertEqual(len(self.userstore.get_all()),2)
|
||||||
|
|
||||||
|
def add_user(self, test_username, add_to_expert_group, email=''):
|
||||||
|
test_user = self.create_user(test_username, email)
|
||||||
|
if add_to_expert_group:
|
||||||
|
test_user = self.add_user_to_expert_group(test_user)
|
||||||
|
self.userstore.set(test_username,test_user)
|
||||||
|
return test_user
|
||||||
|
|
||||||
|
def create_user(self, username, email=''):
|
||||||
|
test_user = User()
|
||||||
|
test_user["username"] = username
|
||||||
|
test_user["email"] = email
|
||||||
|
return test_user
|
||||||
|
|
||||||
|
def add_user_to_expert_group(self, user):
|
||||||
|
user["groups"] = ["expert"]
|
||||||
|
return user
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
BIN
tests/testdata/users.sqlite3
vendored
Normal file
BIN
tests/testdata/users.sqlite3
vendored
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user