[ckan-changes] [okfn/ckan] bdc5ca: Revert "Remove authgroups UI and tests (that rely ...

GitHub noreply at github.com
Tue May 1 17:37:42 UTC 2012


  Branch: refs/heads/enhancement-kill-authgroups-ui
  Home:   https://github.com/okfn/ckan
  Commit: bdc5ca3994a6e96cdff877b371b912d84f40993b
      https://github.com/okfn/ckan/commit/bdc5ca3994a6e96cdff877b371b912d84f40993b
  Author: amercader <amercadero at gmail.com>
  Date:   2012-05-01 (Tue, 01 May 2012)

  Changed paths:
    M ckan/config/routing.py
    A ckan/controllers/authorization_group.py
    M ckan/forms/authz.py
    M ckan/lib/helpers.py
    M ckan/templates/_util.html
    M ckan/templates/admin/authz.html
    A ckan/templates/authorization_group/__init__.py
    A ckan/templates/authorization_group/authz.html
    A ckan/templates/authorization_group/edit.html
    A ckan/templates/authorization_group/edit_form.html
    A ckan/templates/authorization_group/index.html
    A ckan/templates/authorization_group/layout.html
    A ckan/templates/authorization_group/new.html
    A ckan/templates/authorization_group/read.html
    M ckan/templates/group/authz.html
    M ckan/templates/layout_base.html
    M ckan/templates/package/authz.html
    M ckan/tests/functional/test_admin.py
    A ckan/tests/functional/test_authorization_group.py
    M ckan/tests/functional/test_edit_authz.py
    M ckan/tests/functional/test_package_edit_authz.py

  Log Message:
  -----------
  Revert "Remove authgroups UI and tests (that rely on that UI) and the controller)"

This reverts commit cbb83782e3d6a6ff4f0f853cf3afb165f1ae2f90.

Reverting last commit to implement just a subset of the removal for
release 1.7


diff --git a/ckan/config/routing.py b/ckan/config/routing.py
index b048798..43fa8fd 100644
--- a/ckan/config/routing.py
+++ b/ckan/config/routing.py
@@ -216,6 +216,17 @@ def make_map():
     register_package_plugins(map)
     register_group_plugins(map)
 
+    # authz group
+    map.redirect('/authorizationgroups', '/authorizationgroup')
+    map.redirect('/authorizationgroups/{url:.*}', '/authorizationgroup/{url}')
+    with SubMapper(map, controller='authorization_group') as m:
+        m.connect('/authorizationgroup', action='index')
+        m.connect('/authorizationgroup/list', action='list')
+        m.connect('/authorizationgroup/new', action='new')
+        m.connect('/authorizationgroup/edit/{id}', action='edit')
+        m.connect('/authorizationgroup/authz/{id}', action='authz')
+        m.connect('/authorizationgroup/{id}', action='read')
+
     # tags
     map.redirect('/tags', '/tag')
     map.redirect('/tags/{url:.*}', '/tag/{url}')
diff --git a/ckan/controllers/authorization_group.py b/ckan/controllers/authorization_group.py
new file mode 100644
index 0000000..fa1b91a
--- /dev/null
+++ b/ckan/controllers/authorization_group.py
@@ -0,0 +1,201 @@
+import genshi
+
+from sqlalchemy.orm import eagerload_all
+from ckan.lib.base import *
+from pylons.i18n import get_lang, _
+import ckan.authz as authz
+import ckan.forms
+from ckan.lib.helpers import Page
+from ckan.logic import NotAuthorized, check_access
+
+class AuthorizationGroupController(BaseController):
+    
+    def __init__(self):
+        BaseController.__init__(self)
+    
+    def index(self):
+        from ckan.lib.helpers import Page
+        try:
+            context = {'model':model,'user': c.user or c.author}
+            check_access('site_read',context)
+        except NotAuthorized:
+            abort(401, _('Not authorized to see this page'))
+
+        query = ckan.authz.Authorizer().authorized_query(c.user, model.AuthorizationGroup)
+        query = query.options(eagerload_all('users'))
+        c.page = Page(
+            collection=query,
+            page=request.params.get('page', 1),
+            items_per_page=20
+        )
+        return render('authorization_group/index.html')
+
+    def _get_authgroup_by_name_or_id(self, id):
+        return model.AuthorizationGroup.by_name(id) or\
+               model.Session.query(model.AuthorizationGroup).get(id)
+
+    def read(self, id):
+        c.authorization_group = self._get_authgroup_by_name_or_id(id)
+        if c.authorization_group is None:
+            abort(404)
+        auth_for_read = self.authorizer.am_authorized(c, model.Action.READ, 
+                                                      c.authorization_group)
+        if not auth_for_read:
+            abort(401, _('Not authorized to read %s') % id.encode('utf8'))
+        
+        import ckan.misc
+        c.authorization_group_admins = self.authorizer.get_admins(c.authorization_group)
+
+        c.page = Page(
+            collection=c.authorization_group.users,
+            page=request.params.get('page', 1),
+            items_per_page=50
+        )
+        return render('authorization_group/read.html')
+
+    def new(self):
+        record = model.AuthorizationGroup
+        c.error = ''
+        
+        auth_for_create = self.authorizer.am_authorized(c, model.Action.AUTHZ_GROUP_CREATE, model.System())
+        if not auth_for_create:
+            abort(401, _('Unauthorized to create a group'))
+        
+        is_admin = self.authorizer.is_sysadmin(c.user)
+        
+        fs = ckan.forms.get_authorization_group_fieldset(is_admin=is_admin)
+
+        if request.params.has_key('save'):
+            # needed because request is nested
+            # multidict which is read only
+            params = dict(request.params)
+            c.fs = fs.bind(record, data=params or None, session=model.Session)
+            try:
+                self._update(c.fs, id, record.id)
+            except ValidationException, error:
+                fs = error.args[0]
+                c.form = self._render_edit_form(fs)
+                return render('authorization_group/edit.html')
+            # do not use groupname from id as may have changed
+            c.authzgroupname = c.fs.name.value
+            authorization_group = model.AuthorizationGroup.by_name(c.authzgroupname)
+            assert authorization_group
+            user = model.User.by_name(c.user)
+            model.setup_default_user_roles(authorization_group, [user])
+            users = [model.User.by_name(name) for name in \
+                     request.params.getall('AuthorizationGroup-users-current')]
+            authorization_group.users = list(set(users))
+            usernames = request.params.getall('AuthorizationGroupUser--user_name')
+            for username in usernames:
+                if username:
+                    usr = model.User.by_name(username)
+                    if usr and usr not in authorization_group.users:
+                        model.add_user_to_authorization_group(usr, authorization_group, model.Role.READER)
+            model.repo.commit_and_remove()
+            h.redirect_to(controller='authorization_group', action='read', id=c.authzgroupname)
+
+        c.form = self._render_edit_form(fs)
+        return render('authorization_group/new.html')
+
+    def edit(self, id=None): # allow id=None to allow posting
+        c.error = ''
+        authorization_group = self._get_authgroup_by_name_or_id(id)
+        if authorization_group is None:
+            abort(404, '404 Not Found')
+        am_authz = self.authorizer.am_authorized(c, model.Action.EDIT, authorization_group)
+        if not am_authz:
+            abort(401, _('User %r not authorized to edit %r') % (c.user, id))
+            
+        is_admin = self.authorizer.is_sysadmin(c.user)
+        
+        if not 'save' in request.params:
+            c.authorization_group = authorization_group
+            c.authorization_group_name = authorization_group.name
+            
+            fs = ckan.forms.get_authorization_group_fieldset(is_admin=is_admin).bind(authorization_group)
+            c.form = self._render_edit_form(fs)
+            return render('authorization_group/edit.html')
+        else:
+            # id is the name (pre-edited state)
+            c.authorization_group_name = id
+            # needed because request is nested
+            # multidict which is read only
+            params = dict(request.params)
+            c.fs = ckan.forms.get_authorization_group_fieldset()\
+                .bind(authorization_group, data=params or None)
+            try:
+                self._update(c.fs, id, authorization_group.id)
+                # do not use groupname from id as may have changed
+                c.authorization_group = authorization_group
+                c.authorization_group_name = authorization_group.name
+            except ValidationException, error:
+                fs = error.args[0]
+                c.form = self._render_edit_form(fs)
+                return render('authorization_group/edit.html')
+            user = model.User.by_name(c.user)
+            users = [model.User.by_name(name) for name in \
+                     request.params.getall('AuthorizationGroup-users-current')]
+            authorization_group.users = list(set(users))
+            usernames = request.params.getall('AuthorizationGroupUser--user_name')
+            for username in usernames:
+                if username:
+                    usr = model.User.by_name(username)
+                    if usr and usr not in authorization_group.users:
+                        model.add_user_to_authorization_group(usr, authorization_group, model.Role.READER)
+            model.repo.commit_and_remove()
+            h.redirect_to(controller='authorization_group', action='read', id=c.authorization_group_name)
+
+    def authz(self, id):
+        authorization_group = self._get_authgroup_by_name_or_id(id)
+        if authorization_group is None:
+            abort(404, _('Group not found'))
+
+        c.authorization_group_name = authorization_group.name
+        c.authorization_group = authorization_group
+
+        c.authz_editable = self.authorizer.am_authorized(c, model.Action.EDIT_PERMISSIONS, 
+                                                         authorization_group)
+        if not c.authz_editable:
+            abort(401, gettext('User %r not authorized to edit %s authorizations') % (c.user, id))
+
+        roles = self._handle_update_of_authz(authorization_group)
+        self._prepare_authz_info_for_render(roles)
+        return render('authorization_group/authz.html')
+
+
+    def _render_edit_form(self, fs):
+        # errors arrive in c.error and fs.errors
+        c.fieldset = fs
+        c.fieldset2 = ckan.forms.get_authorization_group_user_fieldset()
+        return render('authorization_group/edit_form.html')
+
+    def _update(self, fs, group_name, group_id):
+        '''
+        Writes the POST data (associated with a group edit) to the database
+        @input c.error
+        '''
+        validation = fs.validate()
+        if not validation:
+            c.form = self._render_edit_form(fs)
+            raise ValidationException(fs)
+
+        try:
+            fs.sync()
+        except Exception, inst:
+            model.Session.rollback()
+            raise
+        else:
+            model.Session.commit()
+
+    def _update_authz(self, fs):
+        validation = fs.validate()
+        if not validation:
+            c.form = self._render_edit_form(fs)
+            raise ValidationException(fs)
+        try:
+            fs.sync()
+        except Exception, inst:
+            model.Session.rollback()
+            raise
+        else:
+            model.Session.commit()
diff --git a/ckan/forms/authz.py b/ckan/forms/authz.py
index e0e9628..ac7c512 100644
--- a/ckan/forms/authz.py
+++ b/ckan/forms/authz.py
@@ -30,6 +30,15 @@ def get_group_linker(action):
                         action,
                         action)
 
+def get_authorization_group_linker(action):
+    return lambda item: '<a href="%s" title="%s"><img src="http://m.okfn.org/kforge/images/icon-delete.png" alt="%s" class="icon" /></a>' % (
+                        ckan_h.url_for(controller='authorization_group',
+                            action='authz',
+                            id=item.authorization_group.name,
+                            role_to_delete=item.id),
+                        action,
+                        action)
+
 class RolesRenderer(formalchemy.fields.FieldRenderer):
     def render(self, **kwargs):
         selected = kwargs.get('selected', None) or unicode(self.value)
@@ -49,14 +58,18 @@ def authz_fieldset_builder(role_class):
         fs.append(
             Field(u'delete', types.String, get_group_linker(u'delete')).readonly()
             )
-
+    elif role_class == model.AuthorizationGroupRole:
+        fs.append(
+            Field(u'delete', types.String, get_authorization_group_linker(u'delete')).readonly()
+            )
+        
     fs.append(
             # use getattr because though we should always have a user name,
             # sometimes (due to error) we don't and want to avoid a 500 ...
             Field(u'username', types.String,
                 lambda item: ckan_h.linked_user(getattr(item.user, 'name', ''))).readonly()
             )
-
+            
     fs.append(
             Field(u'authzgroupname', types.String,
                 lambda item: getattr(item.authorized_group, 'name', '')).readonly()
@@ -68,7 +81,8 @@ def authz_fieldset_builder(role_class):
             ],
         include=[fs.username,
                  fs.authzgroupname,
-                 fs.role]
+                 fs.role,
+                 fs.delete],
         )
     return fs
 
@@ -104,7 +118,7 @@ def get_new_role_fieldset(role_class):
 
 fieldsets = {}
 def get_authz_fieldset(name):
-    if not fieldsets:
+    if not fieldsets: 
         fieldsets['package_authz_fs'] = authz_fieldset_builder(model.PackageRole)
         fieldsets['group_authz_fs'] = authz_fieldset_builder(model.GroupRole)
         fieldsets['authorization_group_authz_fs'] = authz_fieldset_builder(model.AuthorizationGroupRole)
diff --git a/ckan/lib/helpers.py b/ckan/lib/helpers.py
index bb51502..ea8a4fe 100644
--- a/ckan/lib/helpers.py
+++ b/ckan/lib/helpers.py
@@ -438,6 +438,20 @@ def linked_user(user, maxlength=0):
         return _icon + link_to(displayname,
                        url_for(controller='user', action='read', id=_name))
 
+def linked_authorization_group(authgroup, maxlength=0):
+    from ckan import model
+    if not isinstance(authgroup, model.AuthorizationGroup):
+        authgroup_name = unicode(authgroup)
+        authgroup = model.AuthorizationGroup.get(authgroup_name)
+        if not authgroup:
+            return authgroup_name
+    if authgroup:
+        displayname = authgroup.name or authgroup.id
+        if maxlength and len(display_name) > maxlength:
+            displayname = displayname[:maxlength] + '...'
+        return link_to(displayname,
+                       url_for(controller='authorization_group', action='read', id=displayname))
+
 def group_name_to_title(name):
     from ckan import model
     group = model.Group.by_name(name)
@@ -757,6 +771,7 @@ def process_names(items):
          #  am_authorized, # depreciated
            'check_access',
            'linked_user',
+           'linked_authorization_group',
            'group_name_to_title',
            'markdown_extract',
            'icon',
diff --git a/ckan/templates/_util.html b/ckan/templates/_util.html
index b76d73a..5aa7c07 100644
--- a/ckan/templates/_util.html
+++ b/ckan/templates/_util.html
@@ -89,6 +89,20 @@
     </py:for>
   </table>
 
+  <!--! List of authorization groups: pass in a collection of authorization groups and
+        this renders the standard group listing -->
+  <table class="table table-bordered table-striped table-condensed authorization_groups" py:def="authorization_group_list(authorization_groups)">
+      <tr><th>Title</th><th>Number of members</th></tr>
+      <py:for each="authorization_group in authorization_groups">
+      <tr>
+        <td><a href="${h.url_for(controller='authorization_group', action='read', id=authorization_group.name or authorization_group.id)}">
+            ${authorization_group.name or authorization_group.id}</a>
+        </td>
+        <td>${len(authorization_group.users)}</td>
+      </tr>
+      </py:for>
+    </table>
+
   <!--! Dataset openness icons -->
   <img py:def="package_license_icon(package)"
     src="${h.url_for('/images/icons/door_%s.png' % 'open' if package.isopen() else 'grey')}"
@@ -195,6 +209,42 @@ <h5 class="heading" title="${related.title}">${h.markdown_extract(related.title,
     </py:for>
   </table>
 
+<!--! Copy and paste of above table. Only difference when created was the h.linked_user for the  -->
+<!--! table rows. How to combine the two? -->
+  <table class="table table-bordered table-striped table-condensed" py:def="authz_form_group_table(id, roles, users, user_role_dict)">
+    <tr>
+      <th>User Group</th>
+      <py:for each="role in roles">
+      <th> ${role} </th>
+      </py:for>
+    </tr>
+    <py:for each="user in users">
+      <tr>
+        <td>
+          ${h.linked_authorization_group(user)}
+        </td>
+        <py:for each="role in roles">
+          <td>
+            <input type="hidden" name="${ h.literal( '%s$%s' % (user,role)) }" value="submitted"/>
+            <py:choose>
+              <py:when test="user_role_dict[(user,role)]">
+                <input type="checkbox"
+                   name="${ h.literal( '%s$%s' % (user,role)) }"
+                   checked='checked'/>
+              </py:when>
+              <py:otherwise>
+                <input type="checkbox"
+                       name="${ h.literal( '%s$%s' % (user,role)) }"
+                       />
+              </py:otherwise>
+            </py:choose>
+          </td>
+        </py:for>
+      </tr>
+    </py:for>
+  </table>
+
+
 
   <table class="table table-bordered table-striped table-condensed" py:def="authz_add_table(roles)">
     <tr>
diff --git a/ckan/templates/admin/authz.html b/ckan/templates/admin/authz.html
index f409f7a..7feaebd 100644
--- a/ckan/templates/admin/authz.html
+++ b/ckan/templates/admin/authz.html
@@ -26,6 +26,25 @@
 
     <hr/>
 
+    <h3>Existing Roles for Authorization Groups</h3>
+
+    <form id="authzgroup_form" method="POST">
+      ${authz_form_group_table('authzgroup_form', c.roles, c.authz_groups, c.authz_groups_role_dict)}
+      <button type="submit" name="authz_save" class="btn btn-primary">Save Changes</button>
+      <div class="clear"></div>
+    </form>
+
+    <h3>Add Roles for Any Authorization Group</h3>
+
+    <form id="authzgroup_addform" method="POST">
+      ${authz_add_group_table(c.roles)}
+      <button type="submit" name="authz_add" class="btn btn-primary">Add Role</button>
+      <div class="clear"></div>
+    </form>
+
+
+
+
   </div>
 
   <xi:include href="layout.html" />
diff --git a/ckan/templates/authorization_group/__init__.py b/ckan/templates/authorization_group/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ckan/templates/authorization_group/authz.html b/ckan/templates/authorization_group/authz.html
new file mode 100644
index 0000000..12643d2
--- /dev/null
+++ b/ckan/templates/authorization_group/authz.html
@@ -0,0 +1,46 @@
+<html xmlns:py="http://genshi.edgewall.org/"
+  xmlns:xi="http://www.w3.org/2001/XInclude"
+  py:strip="">
+  
+  <py:def function="page_title">${c.authorization_group_name} - Authorization - AuthorizationGroups</py:def>
+  <py:def function="page_heading">Authorization: ${c.authorization_group_name}</py:def>
+
+  <div py:match="content">
+    <h3>Update Existing Roles</h3>
+
+    <form id="theform" method="POST">
+      ${authz_form_table('theform', c.roles, c.users, c.user_role_dict)}
+      <button type="submit" name="save">
+        Save
+      </button>
+    </form>
+
+    <h3>Add Roles for Any User</h3>
+
+    <form id="addform" method="POST">
+      ${authz_add_table(c.roles)}
+      <button type="submit" name="add"> Add </button>
+    </form>
+
+    <hr/>
+
+    <h3>Existing Roles for Authorization Groups</h3>
+
+    <form id="authzgroup_form" method="POST">
+      ${authz_form_group_table('authzgroup_form', c.roles, c.authz_groups, c.authz_groups_role_dict)}
+      <button type="submit" name="authz_save">
+        Save
+      </button>
+    </form>
+
+    <h3>Add Roles for Any Authorization Group</h3>
+
+    <form id="authzgroup_addform" method="POST">
+      ${authz_add_group_table(c.roles)}
+      <button type="submit" name="authz_add"> Add </button>
+    </form>
+
+  </div>
+
+  <xi:include href="layout.html" />
+</html>
diff --git a/ckan/templates/authorization_group/edit.html b/ckan/templates/authorization_group/edit.html
new file mode 100644
index 0000000..ade0f54
--- /dev/null
+++ b/ckan/templates/authorization_group/edit.html
@@ -0,0 +1,14 @@
+<html xmlns:py="http://genshi.edgewall.org/"
+  xmlns:xi="http://www.w3.org/2001/XInclude"
+  py:strip="">
+  
+  <py:def function="page_title">${c.authorization_group_name} - Edit - Authorization Groups</py:def>
+  <py:def function="page_heading">Edit: ${c.authorization_group.name if c.authorization_group else ''}</py:def>
+
+  <div py:match="content">
+    ${Markup(c.form)}
+  </div>
+
+  <xi:include href="layout.html" />
+</html>
+
diff --git a/ckan/templates/authorization_group/edit_form.html b/ckan/templates/authorization_group/edit_form.html
new file mode 100644
index 0000000..2934280
--- /dev/null
+++ b/ckan/templates/authorization_group/edit_form.html
@@ -0,0 +1,30 @@
+<form 
+  class="form-horizontal ${'has-errors' if c.errors or c.fieldset.errors else ''}" 
+  id="group-edit" 
+  action="" 
+  method="post"
+  xmlns:py="http://genshi.edgewall.org/"
+  xmlns:xi="http://www.w3.org/2001/XInclude"
+  >
+
+ ${h.literal(c.fieldset.render())}
+
+ <fieldset>
+  <legend>Users</legend>
+  <div class="control-group" py:for="user in c.fieldset.model.users">
+    <label class="control-label" for="AuthorizationGroup-users_${user.name}" style="display: inline;">${user.name}</label>
+    <div class="controls">
+      <label class="checkbox">
+        <input checked="checked" id="AuthorizationGroup-users-current"
+        name="AuthorizationGroup-users-current" type="checkbox" value="${user.name}" />
+      </label>
+    </div>
+  </div>
+  <p py:if="not c.fieldset.model.users">There are no users currently in this group.</p>
+ </fieldset>
+ 
+ ${h.literal(c.fieldset2.render())}
+
+  <br/>
+  ${h.submit('save', _('Save'))}
+</form>
diff --git a/ckan/templates/authorization_group/index.html b/ckan/templates/authorization_group/index.html
new file mode 100644
index 0000000..a135ac9
--- /dev/null
+++ b/ckan/templates/authorization_group/index.html
@@ -0,0 +1,18 @@
+<html xmlns:py="http://genshi.edgewall.org/"
+  xmlns:i18n="http://genshi.edgewall.org/i18n"
+  xmlns:xi="http://www.w3.org/2001/XInclude"
+  py:strip="">
+  
+  <py:def function="page_title">Authorization Groups</py:def>
+  <py:def function="page_heading">Authorization Groups</py:def>
+  
+  <div py:match="content">
+    <p i18n:msg="item_count">There are <strong>${c.page.item_count}</strong> authorization groups.</p>
+
+    ${c.page.pager()}
+    ${authorization_group_list(c.page.items)}
+    ${c.page.pager()}
+  </div>
+
+  <xi:include href="layout.html" />
+</html>
diff --git a/ckan/templates/authorization_group/layout.html b/ckan/templates/authorization_group/layout.html
new file mode 100644
index 0000000..e575f8d
--- /dev/null
+++ b/ckan/templates/authorization_group/layout.html
@@ -0,0 +1,43 @@
+<html
+  xmlns="http://www.w3.org/1999/xhtml"
+  xmlns:i18n="http://genshi.edgewall.org/i18n"
+  xmlns:py="http://genshi.edgewall.org/"
+  xmlns:xi="http://www.w3.org/2001/XInclude"
+  py:strip=""
+  >
+
+  <py:match path="minornavigation">
+  <ul class="nav nav-pills">
+    <li class="${'active' if c.action=='index' else ''}">${h.subnav_link(h.icon('page_white_stack') + _('List'), controller='authorization_group', action='index')}</li>
+    <py:if test="c.authorization_group">
+      <li class="divider">|</li>
+      <li class="${'active' if c.action=='read' else ''}">${h.subnav_link(h.icon('authorization_group') + _('View'), controller='authorization_group', action='read', id=c.authorization_group.name or c.authorization_group.id)}</li>
+      <li class="${'active' if c.action=='edit' else ''}" py:if="h.check_access('authorization_group_update',{'id':c.authorization_group.id})">
+        ${h.subnav_link(h.icon('authorization_group_edit') + _('Edit'), controller='authorization_group', action='edit', id=c.authorization_group.name or c.authorization_group.id)}
+      </li>
+      <li class="${'active' if c.action=='authz' else ''}" py:if="h.check_access('authorization_group_edit_permissions',{'id':c.authorization_group.id})">
+        ${h.subnav_link(h.icon('lock') + _('Authorization'), controller='authorization_group', action='authz', id=c.authorization_group.name or c.authorization_group.id)}
+      </li>
+    </py:if>
+  </ul>
+  </py:match>
+
+  <py:match path="primarysidebar">
+    <li class="widget-container widget_text">
+      <h2>Authorization Groups</h2>
+      <p i18n:msg="">Instead of specifying the privileges of specific users on a dataset or group,
+          you can also specify a set of users that will share the same rights. To do that, an    
+          <strong>authorization group</strong> can be set-up and users can be added to it.</p>
+      <p>
+	<span class="ckan_logged_in" style="display: none;" i18n:msg="">
+	  To create a new authorization group, please first <a href="${h.url_for(controller='user',action='login', id=None)}">login</a>.
+	</span>
+	<span class="ckan_logged_out">
+	  <a href="${h.url_for(controller='authorization_group',action='new', id=None)}">Create a new authorization group</a>
+	</span>
+      </p>
+    </li>
+  </py:match>
+  
+  <xi:include href="../layout.html" />
+</html>
diff --git a/ckan/templates/authorization_group/new.html b/ckan/templates/authorization_group/new.html
new file mode 100644
index 0000000..0dbcc2a
--- /dev/null
+++ b/ckan/templates/authorization_group/new.html
@@ -0,0 +1,14 @@
+<html xmlns:py="http://genshi.edgewall.org/"
+  xmlns:xi="http://www.w3.org/2001/XInclude"
+  py:strip="">
+  
+  <py:def function="page_title">New - Authorization Groups</py:def>
+  <py:def function="page_heading">New Authorization Group</py:def>
+
+  <div py:match="content">
+    ${Markup(c.form)}
+  </div>
+
+  <xi:include href="layout.html" />
+</html>
+
diff --git a/ckan/templates/authorization_group/read.html b/ckan/templates/authorization_group/read.html
new file mode 100644
index 0000000..01ecec0
--- /dev/null
+++ b/ckan/templates/authorization_group/read.html
@@ -0,0 +1,20 @@
+<html xmlns:py="http://genshi.edgewall.org/"
+  xmlns:i18n="http://genshi.edgewall.org/i18n"
+  xmlns:xi="http://www.w3.org/2001/XInclude"
+  py:strip="">
+  
+  <py:def function="page_title">${c.authorization_group.name} - Authorization Groups</py:def>
+  <py:def function="page_heading">${c.authorization_group.name}</py:def>
+
+  <div py:match="content">
+    <h3>Members</h3>
+    <p i18n:msg="item_count">There are ${c.page.item_count} users in this authorization group.</p>
+    ${c.page.pager()}
+    ${user_list(c.page.items)}
+    ${c.page.pager()}
+  </div>
+
+  <xi:include href="layout.html" />
+</html>
+
+
diff --git a/ckan/templates/group/authz.html b/ckan/templates/group/authz.html
index 1d9d9dc..0b778b2 100644
--- a/ckan/templates/group/authz.html
+++ b/ckan/templates/group/authz.html
@@ -1,10 +1,10 @@
 <html xmlns:py="http://genshi.edgewall.org/"
   xmlns:xi="http://www.w3.org/2001/XInclude"
   py:strip="">
-
+  
   <py:def function="page_title">Authorization: ${c.group.display_name}</py:def>
   <py:def function="page_heading">Authorization: ${c.group.display_name}</py:def>
-
+  
   <div py:match="content">
     <h3>Update Existing Roles</h3>
 
@@ -23,6 +23,23 @@
       <div class="clear"></div>
     </form>
 
+    <hr/>
+
+    <h3>Update Existing Roles for Authorization Groups</h3>
+
+    <form id="authzgroup_form" method="POST">
+      ${authz_form_group_table('authzgroup_form', c.roles, c.authz_groups, c.authz_groups_role_dict)}
+      <button type="submit" name="authz_save" class="btn btn-primary">Save Changes</button>
+      <div class="clear"></div>
+    </form>
+
+    <h3>Add Roles for Any Authorization Group</h3>
+
+    <form id="authzgroup_addform" method="POST">
+      ${authz_add_group_table(c.roles)}
+      <button type="submit" name="authz_add" class="btn btn-primary">Add Role</button>
+      <div class="clear"></div>
+    </form>
 
   </div>
 
diff --git a/ckan/templates/layout_base.html b/ckan/templates/layout_base.html
index f2d51dd..8913814 100644
--- a/ckan/templates/layout_base.html
+++ b/ckan/templates/layout_base.html
@@ -177,6 +177,11 @@ <h3 class="widget-title">Sections</h3>
                 </a>
               </li>
               <li>
+                <a href="${h.url(controller='authorization_group', action='index')}">
+                  Authorization Groups
+                </a>
+              </li>
+              <li>
                 <a href="${h.url_for('ckanadmin_index')}">
                   Site Admin
                 </a>
diff --git a/ckan/templates/package/authz.html b/ckan/templates/package/authz.html
index 5849265..aca6146 100644
--- a/ckan/templates/package/authz.html
+++ b/ckan/templates/package/authz.html
@@ -1,7 +1,7 @@
 <html xmlns:py="http://genshi.edgewall.org/"
   xmlns:xi="http://www.w3.org/2001/XInclude"
   py:strip="">
-
+  
   <py:def function="page_title">Authorization: ${c.pkgtitle or c.pkgname}</py:def>
   <py:def function="page_heading">Authorization: ${c.pkgtitle or c.pkgname}</py:def>
 
@@ -22,6 +22,25 @@
       <button type="submit" name="add" class="btn btn-primary">Add Role</button>
       <div class="clear"></div>
     </form>
+
+    <hr/>
+
+    <h3>Update Existing Roles for Authorization Groups</h3>
+
+    <form id="authzgroup_form" method="POST">
+      ${authz_form_group_table('authzgroup_form', c.roles, c.authz_groups, c.authz_groups_role_dict)}
+      <button type="submit" name="authz_save" class="btn btn-primary">Save Changes</button>
+      <div class="clear"></div>
+    </form>
+
+    <h3>Add Roles for Any Authorization Group</h3>
+
+    <form id="authzgroup_addform" method="POST">
+      ${authz_add_group_table(c.roles)}
+      <button type="submit" name="authz_add" class="btn btn-primary">Add Role</button>
+      <div class="clear"></div>
+    </form>
+
   </div>
 
   <xi:include href="layout.html" />
diff --git a/ckan/tests/functional/test_admin.py b/ckan/tests/functional/test_admin.py
index 448dcda..416f1b0 100644
--- a/ckan/tests/functional/test_admin.py
+++ b/ckan/tests/functional/test_admin.py
@@ -34,7 +34,7 @@ def setup_class(cls):
         # Creating a couple of authorization groups, which are enough to break
         # some things just by their existence
         for ag_name in [u'anauthzgroup', u'anotherauthzgroup']:
-            ag=model.AuthorizationGroup.by_name(ag_name)
+            ag=model.AuthorizationGroup.by_name(ag_name) 
             if not ag: #may already exist, if not create
                 ag=model.AuthorizationGroup(name=ag_name)
                 model.Session.add(ag)
@@ -72,6 +72,10 @@ def get_user_form():
            response = get_response()
            return response.forms['theform']
 
+        def get_authzgroup_form():
+           response = get_response()
+           return response.forms['authzgroup_form']
+
         def check_and_set_checkbox(theform, user, role, should_be, set_to):
            user_role_string = '%s$%s' % (user, role)
            checkboxes = [x for x in theform.fields[user_role_string] \
@@ -94,7 +98,7 @@ def submit(form):
 
         def authz_submit(form):
           return form.submit('authz_save', extra_environ=as_testsysadmin)
-
+            
         # get and store the starting state of the system roles
         original_user_roles = get_system_user_roles()
         original_authzgroup_roles = get_system_authzgroup_roles()
@@ -121,12 +125,17 @@ def authz_submit(form):
 
         # and check that's the state in the database now
         assert get_system_user_roles() == expected_user_roles
+        assert get_system_authzgroup_roles() == expected_authzgroup_roles
 
         # try again, this time we expect the box to be ticked already
         submit(check_and_set_checkbox(get_user_form(), u'visitor', u'admin', True, True))
 
         # performing the action twice shouldn't have changed anything
         assert get_system_user_roles() == expected_user_roles
+        assert get_system_authzgroup_roles() == expected_authzgroup_roles
+
+        # now let's make the authzgroup which already has a system role an admin
+        authz_submit(check_and_set_checkbox(get_authzgroup_form(), u'anauthzgroup', u'admin', False, True))
 
         # update expected state to reflect the change we should just have made
         expected_authzgroup_roles.append((u'anauthzgroup', u'admin'))
@@ -134,12 +143,15 @@ def authz_submit(form):
 
         # check that's happened
         assert get_system_user_roles() == expected_user_roles
+        assert get_system_authzgroup_roles() == expected_authzgroup_roles
 
         # put it back how it was
         submit(check_and_set_checkbox(get_user_form(), u'visitor', u'admin', True, False))
+        authz_submit(check_and_set_checkbox(get_authzgroup_form(), u'anauthzgroup', u'admin', True, False))
 
         # should be back to our starting state
         assert original_user_roles == get_system_user_roles()
+        assert original_authzgroup_roles == get_system_authzgroup_roles()
 
 
         # now test making multiple changes
@@ -150,7 +162,7 @@ def authz_submit(form):
         check_and_set_checkbox(form, u'visitor', u'editor', False, True)
         check_and_set_checkbox(form, u'visitor', u'reader', False,  False)
         check_and_set_checkbox(form, u'logged_in', u'editor', True, False)
-        check_and_set_checkbox(form, u'logged_in', u'reader', False, True)
+        check_and_set_checkbox(form, u'logged_in', u'reader', False, True)      
         submit(form)
 
         roles=get_system_user_roles()
@@ -167,6 +179,8 @@ def get_roles_by_name(user=None, group=None):
                 return [y for (x,y) in get_system_user_roles() if x==user]
             elif group:
                 return [y for (x,y) in get_system_authzgroup_roles() if x==group]
+            else: 
+                assert False, 'miscalled'
 
 
         # now we test the box for giving roles to an arbitrary user
@@ -195,6 +209,20 @@ def get_roles_by_name(user=None, group=None):
         assert get_roles_by_name(group=u'anotherauthzgroup') == [], \
            "should not have roles"
 
+        form = get_response().forms['authzgroup_addform']
+        form.fields['new_user_name'][0].value='anotherauthzgroup'
+        checkbox = [x for x in form.fields['reader'] \
+                        if x.__class__.__name__ == 'Checkbox'][0]
+        assert checkbox.checked == False
+        checkbox.checked=True
+        
+        response = form.submit('authz_add', extra_environ=as_testsysadmin)
+        assert "Authorization Group Added" in response, "don't see flash message"
+
+
+        assert get_roles_by_name(group=u'anotherauthzgroup') == [u'reader'], \
+               "should be a reader now"
+
 
 class TestAdminTrashController(WsgiAppCase):
     def setup(cls):
@@ -238,7 +266,7 @@ def test_purge_package(self):
         url = url_for('ckanadmin', action='trash')
         response = self.app.get(url, extra_environ=as_testsysadmin)
         assert 'dataset/warandpeace' in response, response
-
+        
         # Check we get correct error message on attempted purge
         form = response.forms['form-purge-packages']
         response = form.submit('purge-packages', status=[302],
diff --git a/ckan/tests/functional/test_authorization_group.py b/ckan/tests/functional/test_authorization_group.py
new file mode 100644
index 0000000..3d223a5
--- /dev/null
+++ b/ckan/tests/functional/test_authorization_group.py
@@ -0,0 +1,440 @@
+from nose.plugins.skip import SkipTest
+from nose.tools import assert_equal
+
+from ckan.tests import *
+from ckan.authz import Authorizer
+import ckan.model as model
+from base import FunctionalTestCase
+from ckan.tests import search_related
+
+class TestAuthorizationGroup(FunctionalTestCase):
+
+    @classmethod
+    def setup_class(self):
+        model.Session.remove()
+        model.repo.init_db()
+        CreateTestData.create()
+        model.repo.new_revision()
+        treasury = model.AuthorizationGroup(name=u'treasury')
+        health = model.AuthorizationGroup(name=u'health')
+        model.Session.add(treasury)
+        model.Session.add(health)
+        model.add_user_to_authorization_group(model.User.by_name(u"russianfan"), 
+                                              treasury, model.Role.ADMIN)
+        model.repo.commit_and_remove()
+
+    @classmethod
+    def teardown_class(self):
+        model.Session.remove()
+        model.repo.rebuild_db()
+        model.Session.remove()
+
+    def test_index(self):
+        offset = url_for(controller='authorization_group', action='index')
+        res = self.app.get(offset, extra_environ={'REMOTE_USER': 'russianfan'})
+        assert '<h2>Authorization Groups</h2>' in res, res
+        group_count = Authorizer.authorized_query(u'russianfan', model.AuthorizationGroup).count()
+        assert 'There are %s authorization groups.' % group_count in self.strip_tags(res), res
+        authz_groupname = u'treasury'
+        authz_group = model.AuthorizationGroup.by_name(unicode(authz_groupname))
+        group_users_count = len(authz_group.users)
+        self.check_named_element(res, 'tr', authz_groupname, group_users_count)
+        #res = res.click(authz_groupname)
+        #assert authz_groupname in res, res
+        
+    def test_read(self):
+        name = u'treasury'
+        offset = url_for(controller='authorization_group', action='read', id=name)
+        res = self.app.get(offset, extra_environ={'REMOTE_USER': 'russianfan'})
+        main_res = self.main_div(res)
+        assert '%s - Authorization Groups' % name in res, res
+        #assert 'edit' in main_res, main_res
+        assert name in res, res
+
+    def test_new(self):
+        offset = url_for(controller='authorization_group', action='index')
+        res = self.app.get(offset, extra_environ={'REMOTE_USER': 'russianfan'})
+        assert 'Create a new authorization group' in res, res
+        
+
+class TestEdit(TestController):
+    groupname = u'treasury'
+
+    @classmethod
+    def setup_class(self):
+        model.Session.remove()
+        CreateTestData.create()
+        model.repo.new_revision()
+        treasury = model.AuthorizationGroup(name=u'treasury')
+        health = model.AuthorizationGroup(name=u'health')
+        model.Session.add(treasury)
+        model.Session.add(health)
+        model.add_user_to_authorization_group(model.User.by_name(u"russianfan"), 
+                                              treasury, model.Role.ADMIN)
+        model.repo.commit_and_remove()
+        
+        self.username = u'testusr'
+        model.repo.new_revision()
+        model.Session.add(model.User(name=self.username))
+        model.repo.commit_and_remove()
+
+    @classmethod
+    def teardown_class(self):
+        model.Session.remove()
+        model.repo.rebuild_db()
+        model.Session.remove()
+
+    def test_0_not_authz(self):
+        offset = url_for(controller='authorization_group', action='edit', id=self.groupname)
+        # 401 gets caught by repoze.who and turned into redirect
+        res = self.app.get(offset, status=[302, 401])
+        res = res.follow()
+        assert res.request.url.startswith('/user/login')
+
+    def test_1_read_allowed_for_admin(self):
+        raise SkipTest()
+        offset = url_for(controller='authorization_group', action='edit', id=self.groupname)
+        res = self.app.get(offset, status=200, extra_environ={'REMOTE_USER': 'russianfan'})
+        assert 'Edit Authorization Group: %s' % self.groupname in res, res
+        
+    def test_2_edit(self):
+        raise SkipTest()
+        offset = url_for(controller='authorization_group', action='edit', id=self.groupname)
+        res = self.app.get(offset, status=200, extra_environ={'REMOTE_USER': 'russianfan'})
+        assert 'Edit Authorization Group: %s' % self.groupname in res, res
+
+        form = res.forms['group-edit']
+        group = model.AuthorizationGroup.by_name(self.groupname)
+        usr = model.User.by_name(self.username)
+        form['AuthorizationGroupUser--user_name'] = usr.name
+        
+        res = form.submit('save', status=302, extra_environ={'REMOTE_USER': 'russianfan'})
+        # should be read page
+        # assert 'Groups - %s' % self.groupname in res, res
+        
+        model.Session.remove()
+        group = model.AuthorizationGroup.by_name(self.groupname)
+        
+        # now look at packages
+        assert len(group.users) == 2
+
+
+class TestNew(FunctionalTestCase):
+    groupname = u'treasury'
+
+    @classmethod
+    def setup_class(self):
+        CreateTestData.create_user('tester1')
+        CreateTestData.create_user('tester2')
+        CreateTestData.create_user('tester3')
+
+        self.extra_environ = {'REMOTE_USER': 'tester1'}
+
+    @classmethod
+    def teardown_class(self):
+        model.repo.rebuild_db()
+
+    def test_0_new(self):
+        offset = url_for(controller='authorization_group', action='new', id=None)
+        res = self.app.get(offset, status=200, extra_environ=self.extra_environ)
+        assert 'New Authorization Group' in res, res
+
+        form = res.forms['group-edit']
+        form['AuthorizationGroup--name'] = 'testname'
+
+        # can't test users - needs javascript
+        #form['AuthorizationGroupUser--user_name'] = 'tester2' 
+        
+        res = form.submit('save', status=302, extra_environ=self.extra_environ)
+        res = res.follow()
+
+        # should be read page
+        main_res = self.main_div(res)
+        assert 'testname' in main_res, main_res
+
+        # test created object
+        auth_group = model.AuthorizationGroup.by_name('testname')
+        assert auth_group
+        assert_equal(auth_group.name, 'testname')
+
+    def test_0_new_without_name(self):
+        offset = url_for(controller='authorization_group', action='new', id=None)
+        res = self.app.get(offset, status=200, extra_environ=self.extra_environ)
+        assert 'New Authorization Group' in res, res
+
+        form = res.forms['group-edit']
+        # don't set name
+        
+        res = form.submit('save', status=200, extra_environ=self.extra_environ)
+        assert 'Error' in res, res
+        assert 'Name: Please enter a value' in res, res
+
+
+class TestAuthorizationGroupWalkthrough(FunctionalTestCase):
+
+    @classmethod
+    def setup_class(self):
+        model.Session.remove()
+        model.repo.init_db()
+        CreateTestData.create()
+        model.repo.commit_and_remove()
+
+
+    @classmethod
+    def teardown_class(self):
+        model.Session.remove()
+        model.repo.rebuild_db()
+        model.Session.remove()
+
+
+    ## THIS WALKTHROUGH IS NOW COMPLETELY BROKEN BY THE CHANGES I MADE TO THE AUTHZ PAGE
+
+
+    # def test_authzgroups_walkthrough(self):
+    #     # very long test sequence repeating the series of things I did to
+    #     # convince myself that the authzgroups system worked as expected,
+    #     # starting off with the default test data
+        
+    #     # The first thing to notice is that the authzgroup page:
+    #     auth_group_index_url = url_for(controller='/authorization_group', action='index')
+    #     # displays differently for different users.
+
+    #     def get_page(url, expect_status, username, assert_text=None, error_text=None):
+    #         res= self.app.get(url, 
+    #                           status=expect_status, 
+    #                           extra_environ={'REMOTE_USER': username})
+    #         if assert_text and assert_text not in res:
+    #             errorstring = error_text + ' ( "' + assert_text + \
+    #                           '" not found in result of getting "' + \
+    #                           url + '" as user "' + username + '" )'
+    #             assert False, errorstring
+    #         return res
+
+    #     # testsysadmin sees the true picture, where the test data contains two groups
+    #     get_page(auth_group_index_url, 200, 'testsysadmin',
+    #             'There are <strong>2</strong> authorization groups',
+    #             'Should be accurate for testsysadmin')
+
+    #     # But if we look at the same page as annafan, who does not have read 
+    #     # permissions on these groups, we should see neither
+    #     get_page(auth_group_index_url, 200, 'annafan',
+    #             'There are <strong>0</strong> authorization groups',
+    #             'Should lie to annafan about number of groups')
+
+    #     # There is a page for each group
+    #     anauthzgroup_url = url_for(controller='/authorization_group', 
+    #                                action='read', 
+    #                                id='anauthzgroup')
+    #     # And an edit page
+    #     anauthzgroup_edit_url = url_for(controller='/authorization_group',
+    #                                     action='edit', 
+    #                                     id='anauthzgroup')
+
+    #     # testsysadmin should be able to see this, and check that there are no members
+    #     get_page(anauthzgroup_url, 200, 'testsysadmin',
+    #              'There are 0 users in this',
+    #              'should be no users in anauthzgroup')
+
+    #     # now testsysadmin adds annafan to anauthzgroup via the edit page
+    #     res = get_page(anauthzgroup_edit_url, 200, 'testsysadmin')
+    #     group_edit_form = res.forms['group-edit']
+    #     group_edit_form['AuthorizationGroupUser--user_name'] = u'annafan'
+    #     submit_res = group_edit_form.submit('save',
+    #                                   extra_environ={'REMOTE_USER': 'testsysadmin'})
+
+    #     # adding a user to a group should both make her a member, and give her
+    #     # read permission on the group. We'll check those things have actually
+    #     # happened by looking directly in the model.
+    #     anauthzgroup = model.AuthorizationGroup.by_name('anauthzgroup')
+    #     anauthzgroup_users = [x.name for x in anauthzgroup.users]
+    #     anauthzgroup_user_roles = [(x.user.name, x.role) for x in anauthzgroup.roles if x.user]
+    #     assert anauthzgroup_users == [u'annafan'], \
+    #                                      'anauthzgroup should contain annafan (only)'
+    #     assert anauthzgroup_user_roles == [(u'annafan', u'reader')],\
+    #                                      'annafan should be a reader'
+
+    #     # Since annafan has been added to anauthzgroup, which is an admin on
+    #     # anotherauthzgroup, she should now be able to see both the groups.
+    #     get_page(auth_group_index_url, 200, 'annafan',
+    #              'There are <strong>2</strong> auth',
+    #              "annafan should now be able to see both groups")
+
+    #     # When annafan looks at the page for anauthzgroup now
+    #     # She should see that there's one user:
+    #     get_page(anauthzgroup_url, 200,'annafan',
+    #                    'There are 1 users in this', 
+    #                    'annafan should be able to see the list of members')
+
+    #     # Which is her, so her name should be in there somewhere:
+    #     get_page(anauthzgroup_url, 200,'annafan',
+    #                    'annafan', 
+    #                    'annafan should be listed as a member')
+
+    #     # But she shouldn't be able to see the edit page for that group.  
+
+    #     # The behaviour of the test setup here is a bit weird, since in the
+    #     # browser she gets redirected to the login page, but from these tests,
+    #     # she just gets a 401, with no apparent redirect.  Sources inform me
+    #     # that this is normal, and to do with repoze being in the application
+    #     # stack but not in the test stack.
+    #     get_page(anauthzgroup_edit_url, 401, 'annafan',
+    #              'not authorized to edit', 
+    #              'annafan should not be able to edit the list of members')
+    #     # this behaviour also means that we get a flash message left over, which appears on 
+    #     # whatever the next page is.
+  
+    #     # I'm going to assert that behaviour here, just to note it. It's most
+    #     # definitely not required functionality!  We'll do a dummy fetch of the
+    #     # main page for anauthzgroup, which will have the errant flash message
+    #     get_page(anauthzgroup_url, 200, 'annafan',
+    #              'not authorized to edit', 
+    #              'flash message should carry over to next fetch')
+
+    #     # But if we do the dummy fetch twice, the flash message should have gone
+    #     res = get_page(anauthzgroup_url, 200, 'annafan')
+    #     assert 'not authorized to edit' not in res, 'flash message should have gone'
+
+    #     # Since annafan is now a member of anauthzgroup, she should have admin privileges
+    #     # on anotherauthzgroup
+    #     anotherauthzgroup_edit_url = url_for(controller='/authorization_group', 
+    #                                          action='edit', 
+    #                                          id='anotherauthzgroup')
+
+    #     # Which means that she can go to the edit page:
+    #     res = get_page(anotherauthzgroup_edit_url, 200, 'annafan',
+    #              'There are no users',
+    #              "There shouldn't be any users in anotherauthzgroup")
+
+    #     # And change the name of the group
+    #     # The group name editing box has a name dependent on the id of the group,
+    #     # so we find it by regex in the page.
+    #     import re
+    #     p = re.compile('AuthorizationGroup-.*-name')
+    #     groupnamebox = [ v for k,v in res.forms['group-edit'].fields.items() if p.match(k)][0][0]
+    #     groupnamebox.value = 'annasauthzgroup'
+    #     res = res.forms['group-edit'].submit('save', extra_environ={'REMOTE_USER': 'annafan'})
+    #     res = res.follow()
+        
+    #     ## POTENTIAL BUG:
+    #     # note that she could change the name of the group to anauthzgroup,
+    #     # which causes problems due to the name collision. This should be
+    #     # guarded against.
+
+
+    #     # annafan should still be able to see the admin and edit pages of the
+    #     # newly renamed group by virtue of being a member of anauthzgroup
+    #     annasauthzgroup_authz_url = url_for(controller='/authorization_group', 
+    #                                         action='authz', 
+    #                                         id='annasauthzgroup')
+
+    #     annasauthzgroup_edit_url = url_for(controller='/authorization_group', 
+    #                                         action='edit', 
+    #                                         id='annasauthzgroup')
+
+
+    #     res = get_page(annasauthzgroup_authz_url, 200, 'annafan',
+    #                    'Authorization for authorization group: annasauthzgroup',
+    #                    'should be authz page')
+
+    #     # annafan has the power to remove anauthzgroup's admin role on her group
+    #     # The button to remove that role is a link, rather than a submit. I
+    #     # assume there is a better way to do this than searching by regex, but I
+    #     # can't find it.
+    #     import re
+    #     delete_links = re.compile('<a href="(.*)" title="delete">').findall(res.body)
+    #     assert len(delete_links) == 1, "There should only be one delete link here"
+    #     delete_link = delete_links[0]
+
+    #     # Paranoid check, try to follow link without credentials. Should be redirected.
+    #     res = self.app.get(delete_link, status=302)
+    #     res = res.follow()
+    #     assert 'Not authorized to edit authorization for group' in res,\
+    #             "following link without credentials should result in redirection to login page"
+
+    #     # Now follow it as annafan, which should work.
+    #     get_page(delete_link, 200,'annafan',
+    #              "Deleted role 'admin' for authorization group 'anauthzgroup'",
+    #              "Page should mention the deleted role")
+        
+    #     # Trying it a second time should fail since she's now not an admin.
+    #     get_page(delete_link, 401,'annafan')
+ 
+    #     # No one should now have any rights on annasauthzgroup, including
+    #     # annafan herself.  So this should fail too. Again, get a 401 error
+    #     # here, but in the browser we get redirected if we try.
+    #     get_page(annasauthzgroup_authz_url, 401,'annafan')
+
+    #     # testsysadmin can put her back. 
+    #     # It appears that the select boxes on this form need to be set by id
+    #     anauthzgroupid = model.AuthorizationGroup.by_name(u'anauthzgroup').id
+    #     annafanid = model.User.by_name(u'annafan').id
+
+    #     # first try to make both anauthzgroup and annafan editors. This should fail.
+    #     res = get_page(annasauthzgroup_authz_url,200, 'testsysadmin')
+    #     gaf= res.forms['group-authz']
+    #     gaf['AuthorizationGroupRole--authorized_group_id'] = anauthzgroupid
+    #     gaf['AuthorizationGroupRole--role'] = 'editor'
+    #     gaf['AuthorizationGroupRole--user_id'] = annafanid
+    #     res = gaf.submit('save', status=200, extra_environ={'REMOTE_USER': 'testsysadmin'})
+    #     assert 'Please select either a user or an authorization group, not both.' in res,\
+    #          'request should fail if you change both user and authz group'
+
+    #     # settle for just doing one at a time. make anauthzgroup an editor
+    #     res = get_page(annasauthzgroup_authz_url, 200, 'testsysadmin')
+    #     gaf= res.forms['group-authz']
+    #     gaf['AuthorizationGroupRole--authorized_group_id'] = anauthzgroupid
+    #     gaf['AuthorizationGroupRole--role'] = 'editor'
+    #     res = gaf.submit('save',status=200, extra_environ={'REMOTE_USER': 'testsysadmin'})
+    #     assert "Added role 'editor' for authorization group 'anauthzgroup'" in res, \
+    #                                                         "no flash message"
+
+    #     # and make annafan a reader 
+    #     res = get_page(annasauthzgroup_authz_url, 200, 'testsysadmin')
+    #     gaf= res.forms['group-authz']
+    #     gaf['AuthorizationGroupRole--user_id'] = annafanid
+    #     gaf['AuthorizationGroupRole--role'] = 'reader'
+    #     res = gaf.submit('save', status=200, extra_environ={'REMOTE_USER': 'testsysadmin'})
+    #     assert "Added role 'reader' for user 'annafan'" in res, "no flash message"
+
+    #     # annafan should now be able to add her friends to annasauthzgroup
+    #     res = get_page(annasauthzgroup_edit_url, 200, 'annafan')
+    #     res.forms['group-edit']['AuthorizationGroupUser--user_name']='tester'
+    #     # this follows the post/redirect/get pattern
+    #     res = res.forms['group-edit'].submit('save', status=302,
+    #                                          extra_environ={'REMOTE_USER': 'annafan'})
+    #     res = res.follow(status=200, extra_environ={'REMOTE_USER': 'annafan'})
+    #     # and she gets redirected to the group view page
+    #     assert 'tester' in res, 'tester not added?'
+ 
+    #     # she needs to do them one by one
+    #     res = get_page(annasauthzgroup_edit_url, 200, 'annafan',
+    #                    'tester', 
+    #                    'tester not in edit form')
+    #     res.forms['group-edit']['AuthorizationGroupUser--user_name']='russianfan'        
+    #     res = res.forms['group-edit'].submit('save', status=302, extra_environ={'REMOTE_USER': 'annafan'})
+    #     res = res.follow(status=200, extra_environ={'REMOTE_USER': 'annafan'})
+        
+    #     # and finally adds herself
+    #     res = self.app.get(annasauthzgroup_edit_url, status=200, extra_environ={'REMOTE_USER': 'annafan'})
+    #     assert 'russianfan' in res, 'russianfan not added?'
+    #     res.forms['group-edit']['AuthorizationGroupUser--user_name']='annafan'        
+    #     res = res.forms['group-edit'].submit('save', status=302, extra_environ={'REMOTE_USER': 'annafan'})
+    #     res = res.follow(status=200, extra_environ={'REMOTE_USER': 'annafan'})
+    #     assert 'annafan' in res, 'annafan not added?'
+
+    #     # finally let's check that annafan can create a completely new authzgroup
+    #     new_authzgroup_url = url_for(controller='/authorization_group', action='new')
+    #     res = get_page(new_authzgroup_url, 200,'annafan',
+    #                    'New Authorization Group', 
+    #                    "wrong page?")
+    #     gef = res.forms['group-edit']
+    #     gef['AuthorizationGroup--name']="newgroup"
+    #     gef['AuthorizationGroupUser--user_name'] = "russianfan"
+    #     res = gef.submit('save', status=302, extra_environ={'REMOTE_USER': 'annafan'})
+    #     #post/redirect/get
+    #     res = res.follow(status=200, extra_environ={'REMOTE_USER': 'annafan'})
+        
+    #     assert 'newgroup'   in res, "should have redirected to the newgroup page"
+    #     assert 'russianfan' in res, "no russianfan"
+    #     assert 'There are 1 users in this authorization group' in res, "missing text"
+
diff --git a/ckan/tests/functional/test_edit_authz.py b/ckan/tests/functional/test_edit_authz.py
index 4573fbd..941332d 100644
--- a/ckan/tests/functional/test_edit_authz.py
+++ b/ckan/tests/functional/test_edit_authz.py
@@ -77,7 +77,6 @@ def teardown_class(self):
         model.repo.rebuild_db()
 
     def test_access_to_authz(self):
-        raise SkipTest()
         #for each of the three authz pages, check that the access permissions work correctly
         for (c,i) in [('package', self.pkg),('group', self.group),('authorization_group', self.authzgroup)]:
             offset = url_for(controller=c, action='authz', id=i)
@@ -119,7 +118,8 @@ def authzgroup_roles(self):
     # check that the authz page for each object contains certain key strings
     def test_2_read_ok(self):
         for (c,i,m) in [('package', self.pkg, self.package_roles),\
-                        ('group', self.group, self.group_roles)]:
+                        ('group', self.group, self.group_roles),\
+                        ('authorization_group', self.authzgroup, self.authzgroup_roles)]:
             offset = url_for(controller=c, action='authz', id=i)
             res = self.app.get(offset, extra_environ={'REMOTE_USER': self.admin})
             assert i in res, res
@@ -162,7 +162,8 @@ def change_roles(self, user):
         # loop variables here are the controller string, the name of the object we're changing, and three functions,
         # the first fn gets the roles which we'd like to change, and the other two get the roles which we'd like to stay the same.
         for (c,i,var,const1,const2) in [('package', self.pkg, self.package_roles, self.group_roles, self.authzgroup_roles),\
-                        ('group', self.group, self.group_roles, self.package_roles, self.authzgroup_roles)]:
+                        ('group', self.group, self.group_roles, self.package_roles, self.authzgroup_roles),\
+                        ('authorization_group', self.authzgroup, self.authzgroup_roles, self.package_roles, self.group_roles)]:
 
             # load authz page
             offset = url_for(controller=c, action='authz', id=i)
@@ -231,7 +232,8 @@ def delete_role_as(self,user):
         # loop variables here are the controller string, the name of the object we're changing, and three functions,
         # the first fn gets the roles which we'd like to change, and the other two get the roles which we'd like to stay the same.
         for (c,i,var,const1,const2) in [('package', self.pkg, self.package_roles, self.group_roles, self.authzgroup_roles),\
-                        ('group', self.group, self.group_roles, self.package_roles, self.authzgroup_roles)]:
+                        ('group', self.group, self.group_roles, self.package_roles, self.authzgroup_roles),\
+                        ('authorization_group', self.authzgroup, self.authzgroup_roles, self.package_roles, self.group_roles)]:
 
            # get the authz page, check that visitor's in there
            # remove visitor's role on the package
@@ -338,7 +340,8 @@ def add_change_delete_authzgroup_as(self, user):
                          ('logged_in', 'editor')]
 
         for (c,i,var,const1,const2) in [('package', self.pkg, self.package_roles, self.group_roles, self.authzgroup_roles),\
-                        ('group', self.group, self.group_roles, self.package_roles, self.authzgroup_roles)]:
+                        ('group', self.group, self.group_roles, self.package_roles, self.authzgroup_roles),\
+                        ('authorization_group', self.authzgroup, self.authzgroup_roles, self.package_roles, self.group_roles)]:
 
            # get the authz page, check that it contains the object name
            offset = url_for(controller=c, action='authz', id=i)
@@ -412,9 +415,7 @@ def add_change_delete_authzgroup_as(self, user):
 
 
     def test_5_admin_changes_adds_deletes_authzgroup(self):
-        raise SkipTest()
         self.add_change_delete_authzgroup_as(self.admin)
 
     def test_5_sysadmin_changes_adds_deletes_authzgroup(self):
-        raise SkipTest()
         self.add_change_delete_authzgroup_as(self.sysadmin)
diff --git a/ckan/tests/functional/test_package_edit_authz.py b/ckan/tests/functional/test_package_edit_authz.py
index e308643..8f80a27 100644
--- a/ckan/tests/functional/test_package_edit_authz.py
+++ b/ckan/tests/functional/test_package_edit_authz.py
@@ -14,7 +14,7 @@ def setup_class(self):
         # two packages test6 and test6a, m-a is admin on both
         model.repo.init_db()
         model.repo.new_revision()
-
+        
         self.sysadmin = 'madeup-sysadmin'
         sysadmin_user = model.User(name=unicode(self.sysadmin))
         self.admin = 'madeup-administrator'
@@ -51,7 +51,7 @@ def test_0_nonadmin_cannot_edit_authz(self):
         res = self.app.get(offset, status=[302, 401])
         res = res.follow()
         assert res.request.url.startswith('/user/login')
-
+     
     def test_1_admin_has_access(self):
         offset = url_for(controller='package', action='authz', id=self.pkgname)
         res = self.app.get(offset, extra_environ={'REMOTE_USER':
@@ -61,7 +61,7 @@ def test_1_sysadmin_has_access(self):
         offset = url_for(controller='package', action='authz', id=self.pkgname)
         res = self.app.get(offset, extra_environ={'REMOTE_USER':
             self.sysadmin})
-
+    
     def test_2_read_ok(self):
         offset = url_for(controller='package', action='authz', id=self.pkgname)
         res = self.app.get(offset, extra_environ={'REMOTE_USER':
@@ -215,7 +215,7 @@ def delete_role_as(self,user):
         check_and_set_checkbox(form, u'visitor', u'reader', True, False)
         check_and_set_checkbox(form, u'visitor', u'editor', False, True)
         res = form.submit('save', extra_environ={'REMOTE_USER': user})
-
+ 
         # check that the page contains strings for everyone
         assert 'visitor' in res
         assert 'madeup-administrator' in res
@@ -235,3 +235,79 @@ def test_4_sysadmin_deletes_role(self):
         self.delete_role_as(self.sysadmin)
 
 
+    def test_5_add_change_delete_authzgroup(self):
+        user=self.admin
+
+        # get the authz page, check that authzgroup isn't in there
+        offset = url_for(controller='package', action='authz', id=self.pkgname)
+        res = self.app.get(offset, extra_environ={'REMOTE_USER':user})
+        assert self.pkgname in res
+
+        # check the state of the database
+        self.assert_package_roles_to_be([
+           ('madeup-administrator', 'admin'),
+           ('visitor', 'editor'),
+           ('logged_in', 'editor')])
+
+        # and that corresponding user strings are in the authz page
+        assert 'visitor' in res
+        assert 'madeup-administrator' in res
+        assert 'logged_in' in res
+        assert 'madeup-authzgroup' not in res
+
+        # add madeup-authzgroup as an admin
+        form = res.forms['authzgroup_addform']
+        form.fields['new_user_name'][0].value='madeup-authzgroup'
+        checkbox = [x for x in form.fields['admin'] \
+                      if x.__class__.__name__ == 'Checkbox'][0]
+        # check the checkbox is currently unticked
+        assert checkbox.checked == False
+        # tick it and submit
+        checkbox.checked=True
+        res = form.submit('authz_add', extra_environ={'REMOTE_USER':user})
+        assert "User role(s) added" in res, "don't see flash message"
+
+        # examine the new page for user names/authzgroup names
+        assert 'visitor' in res
+        assert 'madeup-administrator' in res
+        assert 'logged_in' in res
+        assert 'madeup-authzgroup' in res
+
+        # and ensure that the database has changed as expected
+        self.assert_package_roles_to_be([
+           ('madeup-authzgroup', 'admin'),
+           ('madeup-administrator', 'admin'),
+           ('visitor', 'editor'),
+           ('logged_in', 'editor')])
+
+        # check that the checkbox states are what we think they should be
+        # and change madeup-authzgroup from admin to editor
+        form = res.forms['authzgroup_form']
+        check_and_set_checkbox(form, u'madeup-authzgroup', u'editor', False, True)
+        check_and_set_checkbox(form, u'madeup-authzgroup', u'admin', True, False)
+        res = form.submit('authz_save', extra_environ={'REMOTE_USER': user})
+
+        #check database has changed.
+        self.assert_package_roles_to_be([
+           ('madeup-authzgroup', 'editor'),
+           ('madeup-administrator', 'admin'),
+           ('visitor', 'editor'),
+           ('logged_in', 'editor')])
+
+        # now remove madeup-authzgroup entirely
+        form = res.forms['authzgroup_form']
+        check_and_set_checkbox(form, u'madeup-authzgroup', u'editor', True, False)
+        check_and_set_checkbox(form, u'madeup-authzgroup', u'admin', False, False)
+        res = form.submit('authz_save', extra_environ={'REMOTE_USER': user})
+
+        #check database is back to normal
+        self.assert_package_roles_to_be([
+           ('madeup-administrator', 'admin'),
+           ('visitor', 'editor'),
+           ('logged_in', 'editor')])
+
+        # and that page contains only the expected strings
+        assert 'visitor' in res
+        assert 'madeup-administrator' in res
+        assert 'logged_in' in res
+        assert 'madeup-authzgroup' not in res


================================================================
  Commit: 3c252f7420114c619fbfbeac5a2f1f11a30e59c7
      https://github.com/okfn/ckan/commit/3c252f7420114c619fbfbeac5a2f1f11a30e59c7
  Author: amercader <amercadero at gmail.com>
  Date:   2012-05-01 (Tue, 01 May 2012)

  Changed paths:
    M ckan/templates/authorization_group/authz.html
    M ckan/templates/authorization_group/edit.html
    M ckan/templates/authorization_group/index.html
    M ckan/templates/authorization_group/new.html
    M ckan/templates/authorization_group/read.html
    M ckan/templates/layout_base.html

  Log Message:
  -----------
  [2366] Removed Auth groups link from footer and added deprecation notice


diff --git a/ckan/templates/authorization_group/authz.html b/ckan/templates/authorization_group/authz.html
index 12643d2..1c5378c 100644
--- a/ckan/templates/authorization_group/authz.html
+++ b/ckan/templates/authorization_group/authz.html
@@ -6,6 +6,12 @@
   <py:def function="page_heading">Authorization: ${c.authorization_group_name}</py:def>
 
   <div py:match="content">
+
+    <div class="alert alert-error">
+        Warning: Authorization groups are deprecated and no longer supported. They will be removed
+        completely on the next CKAN release.
+    </div>
+
     <h3>Update Existing Roles</h3>
 
     <form id="theform" method="POST">
diff --git a/ckan/templates/authorization_group/edit.html b/ckan/templates/authorization_group/edit.html
index ade0f54..a404ce2 100644
--- a/ckan/templates/authorization_group/edit.html
+++ b/ckan/templates/authorization_group/edit.html
@@ -6,6 +6,12 @@
   <py:def function="page_heading">Edit: ${c.authorization_group.name if c.authorization_group else ''}</py:def>
 
   <div py:match="content">
+
+    <div class="alert alert-error">
+        Warning: Authorization groups are deprecated and no longer supported. They will be removed
+        completely on the next CKAN release.
+    </div>
+
     ${Markup(c.form)}
   </div>
 
diff --git a/ckan/templates/authorization_group/index.html b/ckan/templates/authorization_group/index.html
index a135ac9..281f3d1 100644
--- a/ckan/templates/authorization_group/index.html
+++ b/ckan/templates/authorization_group/index.html
@@ -7,6 +7,12 @@
   <py:def function="page_heading">Authorization Groups</py:def>
   
   <div py:match="content">
+
+    <div class="alert alert-error">
+        Warning: Authorization groups are deprecated and no longer supported. They will be removed
+        completely on the next CKAN release.
+    </div>
+
     <p i18n:msg="item_count">There are <strong>${c.page.item_count}</strong> authorization groups.</p>
 
     ${c.page.pager()}
diff --git a/ckan/templates/authorization_group/new.html b/ckan/templates/authorization_group/new.html
index 0dbcc2a..1eb973b 100644
--- a/ckan/templates/authorization_group/new.html
+++ b/ckan/templates/authorization_group/new.html
@@ -6,6 +6,12 @@
   <py:def function="page_heading">New Authorization Group</py:def>
 
   <div py:match="content">
+
+    <div class="alert alert-error">
+        Warning: Authorization groups are deprecated and no longer supported. They will be removed
+        completely on the next CKAN release.
+    </div>
+
     ${Markup(c.form)}
   </div>
 
diff --git a/ckan/templates/authorization_group/read.html b/ckan/templates/authorization_group/read.html
index 01ecec0..3c2c7ba 100644
--- a/ckan/templates/authorization_group/read.html
+++ b/ckan/templates/authorization_group/read.html
@@ -7,6 +7,12 @@
   <py:def function="page_heading">${c.authorization_group.name}</py:def>
 
   <div py:match="content">
+
+    <div class="alert alert-error">
+        Warning: Authorization groups are deprecated and no longer supported. They will be removed
+        completely on the next CKAN release.
+    </div>
+
     <h3>Members</h3>
     <p i18n:msg="item_count">There are ${c.page.item_count} users in this authorization group.</p>
     ${c.page.pager()}
diff --git a/ckan/templates/layout_base.html b/ckan/templates/layout_base.html
index 8913814..f2d51dd 100644
--- a/ckan/templates/layout_base.html
+++ b/ckan/templates/layout_base.html
@@ -177,11 +177,6 @@ <h3 class="widget-title">Sections</h3>
                 </a>
               </li>
               <li>
-                <a href="${h.url(controller='authorization_group', action='index')}">
-                  Authorization Groups
-                </a>
-              </li>
-              <li>
                 <a href="${h.url_for('ckanadmin_index')}">
                   Site Admin
                 </a>


================================================================
Compare: https://github.com/okfn/ckan/compare/cbb8378...3c252f7


More information about the ckan-changes mailing list