summaryrefslogtreecommitdiffstats
path: root/funcweb
diff options
context:
space:
mode:
authorLuke Macken <lmacken@redhat.com>2008-01-24 15:23:00 -0500
committerLuke Macken <lmacken@redhat.com>2008-01-24 15:23:00 -0500
commit355d15f9d450c5997ef74b1fb0fc3745bf2dfe35 (patch)
tree824a4c121efa2e4e63dee3caefa2feb7dffb81f5 /funcweb
parentdf4da5b1811b108b22cf6a4cab5e2fe5d75ef806 (diff)
downloadthird_party-func-355d15f9d450c5997ef74b1fb0fc3745bf2dfe35.tar.gz
third_party-func-355d15f9d450c5997ef74b1fb0fc3745bf2dfe35.tar.xz
third_party-func-355d15f9d450c5997ef74b1fb0fc3745bf2dfe35.zip
Only allow localhost and authenticated users access to funcweb. This entails,
- Utilizing the TurboGears identity framework - Creating our identity model using SQLAlchemy+Elixir
Diffstat (limited to 'funcweb')
-rw-r--r--funcweb/README.txt23
-rw-r--r--funcweb/dev.cfg34
-rw-r--r--funcweb/funcweb/commands.py52
-rw-r--r--funcweb/funcweb/config/app.cfg95
-rw-r--r--funcweb/funcweb/controllers.py48
-rw-r--r--funcweb/funcweb/model.py100
-rw-r--r--funcweb/funcweb/release.py9
-rw-r--r--funcweb/funcweb/templates/master.html3
-rw-r--r--funcweb/sample-prod.cfg19
-rw-r--r--funcweb/setup.py28
-rwxr-xr-xfuncweb/start-funcweb.py35
-rw-r--r--funcweb/test.cfg32
12 files changed, 409 insertions, 69 deletions
diff --git a/funcweb/README.txt b/funcweb/README.txt
index 3374563..4c52dcd 100644
--- a/funcweb/README.txt
+++ b/funcweb/README.txt
@@ -3,14 +3,29 @@ funcweb
A TurboGears interface to func.
-This project is currently under development, and should not be used in an
-production environment. It employs no concept of security, and should only
-be used for testing.
+This project is currently under development, and is currently just a
+proof-of-concept and should not be used in a production environment.
Running
=======
- # ./start-funcweb.py
+ # yum install TurboGears python-genshi python-elixir
+ $ python setup.py egg_info
+ $ tg-admin sql create
+ # ./start-funcweb.py
+
+Connect to http://localhost:8080
+
+Creating a new user
+===================
+
+Currently funcweb only allows connections from 127.0.0.1 and from authenticated
+users. So if you wish to grant other people access to your funcweb instance,
+you can create new users easily:
+
+ $ tg-admin shell
+ >>> user = User(user_name='name', password='password')
+ >>> ^D
Authors
=======
diff --git a/funcweb/dev.cfg b/funcweb/dev.cfg
index 77efc8c..638c92b 100644
--- a/funcweb/dev.cfg
+++ b/funcweb/dev.cfg
@@ -1,28 +1,22 @@
[global]
# This is where all of your settings go for your development environment
# Settings that are the same for both development and production
-# (such as template engine, encodings, etc.) all go in
+# (such as template engine, encodings, etc.) all go in
# funcweb/config/app.cfg
# DATABASE
+# driver://username:password@host:port/database
+
# pick the form for your database
-# sqlobject.dburi="postgres://username@hostname/databasename"
-# sqlobject.dburi="mysql://username:password@hostname:port/databasename"
-# sqlobject.dburi="sqlite:///file_name_and_path"
+# sqlalchemy.dburi="postgres://username@hostname/databasename"
+# sqlalchemy.dburi="mysql://username:password@hostname:port/databasename"
+# sqlalchemy.dburi="sqlite://%(current_dir_uri)s/devdata.sqlite"
# If you have sqlite, here's a simple default to get you started
# in development
-sqlobject.dburi="sqlite://%(current_dir_uri)s/devdata.sqlite"
-
-
-# if you are using a database or table type without transactions
-# (MySQL default, for example), you should turn off transactions
-# by prepending notrans_ on the uri
-# sqlobject.dburi="notrans_mysql://username:password@hostname:port/databasename"
+sqlalchemy.dburi="sqlite:///devdata.sqlite"
-# for Windows users, sqlite URIs look like:
-# sqlobject.dburi="sqlite:///drive_letter:/path/to/file"
# SERVER
@@ -32,6 +26,7 @@ sqlobject.dburi="sqlite://%(current_dir_uri)s/devdata.sqlite"
# Enable the debug output at the end on pages.
# log_debug_info_filter.on = False
+server.socket_host="127.0.0.1"
server.environment="development"
autoreload.package="funcweb"
@@ -64,3 +59,16 @@ level='INFO'
qualname='turbogears.access'
handlers=['access_out']
propagate=0
+
+[[[identity]]]
+level='INFO'
+qualname='turbogears.identity'
+handlers=['access_out']
+propagate=0
+
+[[[database]]]
+# Set to INFO to make SQLAlchemy display SQL commands
+level='ERROR'
+qualname='sqlalchemy.engine'
+handlers=['debug_out']
+propagate=0
diff --git a/funcweb/funcweb/commands.py b/funcweb/funcweb/commands.py
new file mode 100644
index 0000000..043ce1f
--- /dev/null
+++ b/funcweb/funcweb/commands.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+"""This module contains functions called from console script entry points."""
+
+import os
+import sys
+
+from os.path import dirname, exists, join
+
+import pkg_resources
+pkg_resources.require("TurboGears")
+
+import turbogears
+import cherrypy
+
+cherrypy.lowercase_api = True
+
+class ConfigurationError(Exception):
+ pass
+
+def start():
+ """Start the CherryPy application server."""
+
+ setupdir = dirname(dirname(__file__))
+ curdir = os.getcwd()
+
+ # First look on the command line for a desired config file,
+ # if it's not on the command line, then look for 'setup.py'
+ # in the current directory. If there, load configuration
+ # from a file called 'dev.cfg'. If it's not there, the project
+ # is probably installed and we'll look first for a file called
+ # 'prod.cfg' in the current directory and then for a default
+ # config file called 'default.cfg' packaged in the egg.
+ if len(sys.argv) > 1:
+ configfile = sys.argv[1]
+ elif exists(join(setupdir, "setup.py")):
+ configfile = join(setupdir, "dev.cfg")
+ elif exists(join(curdir, "prod.cfg")):
+ configfile = join(curdir, "prod.cfg")
+ else:
+ try:
+ configfile = pkg_resources.resource_filename(
+ pkg_resources.Requirement.parse("funcweb"),
+ "config/default.cfg")
+ except pkg_resources.DistributionNotFound:
+ raise ConfigurationError("Could not find default configuration.")
+
+ turbogears.update_config(configfile=configfile,
+ modulename="funcweb.config")
+
+ from funcweb.controllers import Root
+
+ turbogears.start_server(Root())
diff --git a/funcweb/funcweb/config/app.cfg b/funcweb/funcweb/config/app.cfg
index 24e0807..504f018 100644
--- a/funcweb/funcweb/config/app.cfg
+++ b/funcweb/funcweb/config/app.cfg
@@ -19,16 +19,109 @@ tg.defaultview = "genshi"
# Allow every exposed function to be called as json,
# tg.allow_json = False
+# Suppress the inclusion of the shipped MochiKit version, which is rather outdated.
+# Attention: setting this to True and listing 'turbogears.mochikit' in 'tg.include_widgets'
+# is a contradiction. This option will overrule the default-inclusion to prevent version
+# mismatch bugs.
+# tg.mochikit_suppress = True
+
# List of Widgets to include on every page.
-# for exemple ['turbogears.mochikit']
+# for example ['turbogears.mochikit']
# tg.include_widgets = []
# Set to True if the scheduler should be started
# tg.scheduler = False
+# Set to True to allow paginate decorator redirects when page number gets
+# out of bound. Useful for getting the real page id in the url
+# paginate.redirect_on_out_of_range = True
+
+# Set to True to allow paginate decorator redirects when last page is requested.
+# This is useful for getting the real last page id in the url
+# paginate.redirect_on_last_page = True
+
# Set session or cookie
# session_filter.on = True
+# VISIT TRACKING
+# Each visit to your application will be assigned a unique visit ID tracked via
+# a cookie sent to the visitor's browser.
+# --------------
+
+# Enable Visit tracking
+visit.on=True
+
+# Number of minutes a visit may be idle before it expires.
+# visit.timeout=20
+
+# The name of the cookie to transmit to the visitor's browser.
+# visit.cookie.name="tg-visit"
+
+# Domain name to specify when setting the cookie (must begin with . according to
+# RFC 2109). The default (None) should work for most cases and will default to
+# the machine to which the request was made. NOTE: localhost is NEVER a valid
+# value and will NOT WORK.
+# visit.cookie.domain=None
+
+# Specific path for the cookie
+# visit.cookie.path="/"
+
+# The name of the VisitManager plugin to use for visitor tracking.
+visit.manager="sqlalchemy"
+
+# Database class to use for visit tracking
+visit.saprovider.model = "funcweb.model.Visit"
+identity.saprovider.model.visit = "funcweb.model.VisitIdentity"
+
+# IDENTITY
+# General configuration of the TurboGears Identity management module
+# --------
+
+# Switch to turn on or off the Identity management module
+identity.on=True
+
+# [REQUIRED] URL to which CherryPy will internally redirect when an access
+# control check fails. If Identity management is turned on, a value for this
+# option must be specified.
+identity.failure_url="/login"
+
+identity.provider='sqlalchemy'
+
+# The names of the fields on the login form containing the visitor's user ID
+# and password. In addition, the submit button is specified simply so its
+# existence may be stripped out prior to passing the form data to the target
+# controller.
+# identity.form.user_name="user_name"
+# identity.form.password="password"
+# identity.form.submit="login"
+
+# What sources should the identity provider consider when determining the
+# identity associated with a request? Comma separated list of identity sources.
+# Valid sources: form, visit, http_auth
+# identity.source="form,http_auth,visit"
+
+# SqlAlchemyIdentityProvider
+# Configuration options for the default IdentityProvider
+# -------------------------
+
+# The classes you wish to use for your Identity model. Remember to not use reserved
+# SQL keywords for class names (at least unless you specify a different table
+# name using sqlmeta).
+identity.saprovider.model.user="funcweb.model.User"
+identity.saprovider.model.group="funcweb.model.Group"
+identity.saprovider.model.permission="funcweb.model.Permission"
+
+# The password encryption algorithm used when comparing passwords against what's
+# stored in the database. Valid values are 'md5' or 'sha1'. If you do not
+# specify an encryption algorithm, passwords are expected to be clear text.
+# The SqlAlchemyProvider *will* encrypt passwords supplied as part of your login
+# form. If you set the password through the password property, like:
+# my_user.password = 'secret'
+# the password will be encrypted in the database, provided identity is up and
+# running, or you have loaded the configuration specifying what encryption to
+# use (in situations where identity may not yet be running, like tests).
+
+# identity.saprovider.encryption_algorithm=None
# compress the data sends to the web browser
# [/]
diff --git a/funcweb/funcweb/controllers.py b/funcweb/funcweb/controllers.py
index 0d74a50..df4c05c 100644
--- a/funcweb/funcweb/controllers.py
+++ b/funcweb/funcweb/controllers.py
@@ -1,18 +1,24 @@
import logging
log = logging.getLogger(__name__)
-from turbogears import controllers, expose, flash
+from turbogears import controllers, expose, flash, identity, redirect
from func.overlord.client import Client
class Root(controllers.RootController):
@expose(template="funcweb.templates.minions")
- def minions(self):
- """ Return a list of our minions """
- fc = Client("*")
+ @identity.require(identity.Any(
+ identity.from_host("127.0.0.1"), identity.not_anonymous()))
+ def minions(self, glob='*'):
+ """ Return a list of our minions that match a given glob """
+ fc = Client(glob)
return dict(minions=fc.system.list_methods())
+ index = minions # start with our minion view, for now
+
@expose(template="funcweb.templates.minion")
+ @identity.require(identity.Any(
+ identity.from_host("127.0.0.1"), identity.not_anonymous()))
def minion(self, name, module=None, method=None):
""" Display module or method details for a specific minion.
@@ -34,11 +40,43 @@ class Root(controllers.RootController):
return dict(modules=modules, module=module,
tg_template="funcweb.templates.module")
- index = minions # start with our minion view, for now
@expose(template="funcweb.templates.run")
+ @identity.require(identity.Any(
+ identity.from_host("127.0.0.1"), identity.not_anonymous()))
def run(self, minion="*", module=None, method=None, arguments=''):
fc = Client(minion)
results = getattr(getattr(fc, module), method)(*arguments.split())
cmd = "%s.%s.%s(%s)" % (minion, module, method, arguments)
return dict(cmd=cmd, results=results)
+
+ @expose(template="funcweb.templates.login")
+ def login(self, forward_url=None, previous_url=None, *args, **kw):
+ from cherrypy import request, response
+ if not identity.current.anonymous \
+ and identity.was_login_attempted() \
+ and not identity.get_identity_errors():
+ raise redirect(forward_url)
+
+ forward_url=None
+ previous_url= request.path
+
+ if identity.was_login_attempted():
+ msg=_("The credentials you supplied were not correct or "
+ "did not grant access to this resource.")
+ elif identity.get_identity_errors():
+ msg=_("You must provide your credentials before accessing "
+ "this resource.")
+ else:
+ msg=_("Please log in.")
+ forward_url= request.headers.get("Referer", "/")
+
+ response.status=403
+ return dict(message=msg, previous_url=previous_url, logging_in=True,
+ original_parameters=request.params,
+ forward_url=forward_url)
+
+ @expose()
+ def logout(self):
+ identity.current.logout()
+ raise redirect("/")
diff --git a/funcweb/funcweb/model.py b/funcweb/funcweb/model.py
index c35e930..2997cf0 100644
--- a/funcweb/funcweb/model.py
+++ b/funcweb/funcweb/model.py
@@ -1 +1,99 @@
-# <insert SQLAlchemy hotness here>
+from datetime import datetime
+# import the basic Elixir classes and functions for declaring the data model
+# (see http://elixir.ematia.de/trac/wiki/TutorialDivingIn)
+from elixir import Entity, Field, OneToMany, ManyToOne, ManyToMany
+from elixir import options_defaults, using_options, setup_all
+# import some datatypes for table columns from Elixir
+# (see http://www.sqlalchemy.org/docs/04/types.html for more)
+from elixir import String, Unicode, Integer, DateTime
+from turbogears import identity
+
+options_defaults['autosetup'] = False
+
+
+# your data model
+
+# class YourDataClass(Entity):
+# pass
+
+
+# the identity model
+
+
+class Visit(Entity):
+ """
+ A visit to your site
+ """
+ using_options(tablename='visit')
+
+ visit_key = Field(String(40), primary_key=True)
+ created = Field(DateTime, nullable=False, default=datetime.now)
+ expiry = Field(DateTime)
+
+ @classmethod
+ def lookup_visit(cls, visit_key):
+ return Visit.get(visit_key)
+
+
+class VisitIdentity(Entity):
+ """
+ A Visit that is link to a User object
+ """
+ using_options(tablename='visit_identity')
+
+ visit_key = Field(String(40), primary_key=True)
+ user = ManyToOne('User', colname='user_id', use_alter=True)
+
+
+class Group(Entity):
+ """
+ An ultra-simple group definition.
+ """
+ using_options(tablename='tg_group')
+
+ group_id = Field(Integer, primary_key=True)
+ group_name = Field(Unicode(16), unique=True)
+ display_name = Field(Unicode(255))
+ created = Field(DateTime, default=datetime.now)
+ users = ManyToMany('User', tablename='user_group')
+ permissions = ManyToMany('Permission', tablename='group_permission')
+
+
+class User(Entity):
+ """
+ Reasonably basic User definition.
+ Probably would want additional attributes.
+ """
+ using_options(tablename='tg_user')
+
+ user_id = Field(Integer, primary_key=True)
+ user_name = Field(Unicode(16), unique=True)
+ email_address = Field(Unicode(255), unique=True)
+ display_name = Field(Unicode(255))
+ password = Field(Unicode(40))
+ created = Field(DateTime, default=datetime.now)
+ groups = ManyToMany('Group', tablename='user_group')
+
+ @property
+ def permissions(self):
+ perms = set()
+ for g in self.groups:
+ perms |= set(g.permissions)
+ return perms
+
+
+class Permission(Entity):
+ """
+ A relationship that determines what each Group can do
+ """
+ using_options(tablename='permission')
+
+ permission_id = Field(Integer, primary_key=True)
+ permission_name = Field(Unicode(16), unique=True)
+ description = Field(Unicode(255))
+ groups = ManyToMany('Group', tablename='group_permission')
+
+
+# Set up all Elixir entities declared above
+
+setup_all()
diff --git a/funcweb/funcweb/release.py b/funcweb/funcweb/release.py
new file mode 100644
index 0000000..b6593d4
--- /dev/null
+++ b/funcweb/funcweb/release.py
@@ -0,0 +1,9 @@
+# Release information about funcweb
+
+version = "0.1"
+description = "A web interface to func"
+author = "Luke Macken"
+email = "lmacken@redhat.com"
+copyright = "2008 Red Hat, Inc."
+url = "http://fedorahosted.org/func"
+license = "GPL"
diff --git a/funcweb/funcweb/templates/master.html b/funcweb/funcweb/templates/master.html
index 8d09254..ab14ca0 100644
--- a/funcweb/funcweb/templates/master.html
+++ b/funcweb/funcweb/templates/master.html
@@ -34,6 +34,7 @@
</div>
</div>
<div class="content">
+ <!--
<div py:if="tg.config('identity.on',False) and not 'logging_in' in locals()" id="pageLogin" class="usernav">
<span py:if="tg.identity.anonymous">
You are not logged in yet <a class="loginButton" href="${tg.url('/login/')}">login</a>
@@ -43,7 +44,7 @@
<a class="loginButton" href="${tg.url('/logout/')}">logout</a>
</span>
</div>
-
+ -->
<div py:replace="select('*|text()')" />
</div>
</div>
diff --git a/funcweb/sample-prod.cfg b/funcweb/sample-prod.cfg
index 6ca2b4e..7f6cc6e 100644
--- a/funcweb/sample-prod.cfg
+++ b/funcweb/sample-prod.cfg
@@ -8,23 +8,16 @@
# DATABASE
+# driver://username:password@host:port/database
+
# pick the form for your database
-# sqlobject.dburi="postgres://username@hostname/databasename"
-# sqlobject.dburi="mysql://username:password@hostname:port/databasename"
-# sqlobject.dburi="sqlite:///file_name_and_path"
+# sqlalchemy.dburi="postgres://username@hostname/databasename"
+# sqlalchemy.dburi="mysql://username:password@hostname:port/databasename"
+# sqlalchemy.dburi="sqlite:///file_name_and_path"
# If you have sqlite, here's a simple default to get you started
# in development
-sqlobject.dburi="sqlite://%(current_dir_uri)s/devdata.sqlite"
-
-
-# if you are using a database or table type without transactions
-# (MySQL default, for example), you should turn off transactions
-# by prepending notrans_ on the uri
-# sqlobject.dburi="notrans_mysql://username:password@hostname:port/databasename"
-
-# for Windows users, sqlite URIs look like:
-# sqlobject.dburi="sqlite:///drive_letter:/path/to/file"
+sqlalchemy.dburi="sqlite:///%(current_dir_uri)s/devdata.sqlite"
# SERVER
diff --git a/funcweb/setup.py b/funcweb/setup.py
index 7ba1a53..9bde340 100644
--- a/funcweb/setup.py
+++ b/funcweb/setup.py
@@ -1,7 +1,10 @@
+# -*- coding: utf-8 -*-
+
from setuptools import setup, find_packages
from turbogears.finddata import find_package_data
import os
+execfile(os.path.join("funcweb", "release.py"))
packages=find_packages()
package_data = find_package_data(where='funcweb',
@@ -14,18 +17,16 @@ if os.path.isdir('locales'):
setup(
name="funcweb",
version=version,
-
- #description=description,
- author="Luke Macken",
- author_email="lmacken@redhat.com",
- #url=url,
- #download_url=download_url,
- #license=license,
+ description=description,
+ author=author,
+ author_email=email,
+ url=url,
+ license=license,
install_requires=[
- "TurboGears >= 1.0.3.2",
+ "TurboGears >= 1.0.4.2",
+ "SQLAlchemy>=0.3.10",
],
- scripts=["start-funcweb.py"],
zip_safe=False,
packages=packages,
package_data=package_data,
@@ -63,5 +64,12 @@ setup(
# 'Framework :: TurboGears :: Widgets',
],
test_suite='nose.collector',
+ entry_points = {
+ 'console_scripts': [
+ 'start-funcweb = funcweb.commands:start',
+ ],
+ },
+ # Uncomment next line and create a default.cfg file in your project dir
+ # if you want to package a default configuration in your egg.
+ #data_files = [('config', ['default.cfg'])],
)
-
diff --git a/funcweb/start-funcweb.py b/funcweb/start-funcweb.py
index 604cf19..3d375a3 100755
--- a/funcweb/start-funcweb.py
+++ b/funcweb/start-funcweb.py
@@ -1,25 +1,18 @@
#!/usr/bin/python
-__requires__="TurboGears"
-import pkg_resources
+# -*- coding: utf-8 -*-
+"""Start script for the funcweb TurboGears project.
-from turbogears import config, update_config, start_server
-import cherrypy
-cherrypy.lowercase_api = True
-from os.path import *
-import sys
+This script is only needed during development for running from the project
+directory. When the project is installed, easy_install will create a
+proper start script.
+"""
-# first look on the command line for a desired config file,
-# if it's not on the command line, then
-# look for setup.py in this directory. If it's not there, this script is
-# probably installed
-if len(sys.argv) > 1:
- update_config(configfile=sys.argv[1],
- modulename="funcweb.config")
-elif exists(join(dirname(__file__), "setup.py")):
- update_config(configfile="dev.cfg",modulename="funcweb.config")
-else:
- update_config(configfile="prod.cfg",modulename="funcweb.config")
-config.update(dict(package="funcweb"))
+import sys
+from funcweb.commands import start, ConfigurationError
-from funcweb.controllers import Root
-start_server(Root())
+if __name__ == "__main__":
+ try:
+ start()
+ except ConfigurationError, exc:
+ sys.stderr.write(str(exc))
+ sys.exit(1)
diff --git a/funcweb/test.cfg b/funcweb/test.cfg
new file mode 100644
index 0000000..4140d5a
--- /dev/null
+++ b/funcweb/test.cfg
@@ -0,0 +1,32 @@
+[global]
+# You can place test-specific configuration options here (like test db uri, etc)
+
+# DATABASE
+
+sqlalchemy.dburi = "sqlite:///:memory:"
+
+# LOGGING
+
+[logging]
+
+[[formatters]]
+[[[full_content]]]
+format='*(asctime)s *(name)s *(levelname)s *(message)s'
+
+[[handlers]]
+[[[test_out]]]
+class='StreamHandler'
+level='DEBUG'
+args='(sys.stdout,)'
+formatter='full_content'
+
+[[loggers]]
+[[[funcweb]]]
+level='DEBUG'
+qualname='funcweb'
+handlers=['test_out']
+
+[[[turbogears]]]
+level='INFO'
+qualname='turbogears'
+handlers=['test_out']