summaryrefslogtreecommitdiffstats
path: root/funcweb/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/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/funcweb')
-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
6 files changed, 299 insertions, 8 deletions
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>