You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by je...@apache.org on 2014/07/22 14:33:00 UTC

[1/4] git commit: [#7524] ticket:613 Site admin page for users audit log

Repository: allura
Updated Branches:
  refs/heads/je/42cc_7524 [created] c03d10d1f


[#7524] ticket:613 Site admin page for users audit log


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/ab49aa3d
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/ab49aa3d
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/ab49aa3d

Branch: refs/heads/je/42cc_7524
Commit: ab49aa3d517686ad5f164543289ac95fc4f4563b
Parents: e297862
Author: Igor Bondarenko <je...@gmail.com>
Authored: Mon Jul 21 15:07:06 2014 +0300
Committer: Igor Bondarenko <je...@gmail.com>
Committed: Mon Jul 21 15:07:06 2014 +0300

----------------------------------------------------------------------
 Allura/allura/controllers/site_admin.py         | 30 ++++++++++++++++
 Allura/allura/model/auth.py                     |  4 +++
 .../templates/site_admin_users_audit.html       | 37 ++++++++++++++++++++
 3 files changed, 71 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/ab49aa3d/Allura/allura/controllers/site_admin.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/site_admin.py b/Allura/allura/controllers/site_admin.py
index 3cf4973..96229ee 100644
--- a/Allura/allura/controllers/site_admin.py
+++ b/Allura/allura/controllers/site_admin.py
@@ -36,6 +36,7 @@ from allura.lib.decorators import require_post
 from allura.lib.plugin import SiteAdminExtension
 from allura.lib.security import require_access
 from allura.lib.widgets import form_fields as ffw
+from allura.ext.admin.widgets import AuditLog
 from allura import model as M
 from allura.command.show_models import dfs, build_model_inheritance_graph
 import allura
@@ -49,6 +50,7 @@ log = logging.getLogger(__name__)
 class W:
     page_list = ffw.PageList()
     page_size = ffw.PageSize()
+    audit = AuditLog()
 
 
 class SiteAdminController(object):
@@ -79,6 +81,7 @@ class SiteAdminController(object):
             SitemapEntry('New Projects', base_url + 'new_projects', ui_icon=g.icons['admin']),
             SitemapEntry('Reclone Repo', base_url + 'reclone_repo', ui_icon=g.icons['admin']),
             SitemapEntry('Task Manager', base_url + 'task_manager?state=busy', ui_icon=g.icons['stats']),
+            SitemapEntry('Users Audit Log', base_url + 'users', ui_icon=g.icons['admin']),
         ]
         for ep_name in sorted(g.entry_points['site_admin']):
             g.entry_points['site_admin'][ep_name]().update_sidebar_menu(links)
@@ -239,6 +242,33 @@ class SiteAdminController(object):
             mount_point = ''
         return dict(prefix=prefix, shortname=shortname, mount_point=mount_point)
 
+    @expose('jinja:allura:templates/site_admin_users_audit.html')
+    def users(self, username=None, limit=10, page=0, **kwargs):
+        user = M.User.by_username(username)
+        limit = int(limit)
+        page = int(page)
+        if user is None or user.is_anonymous():
+            return dict(
+                entries=[],
+                limit=limit,
+                page=page,
+                count=0,
+                username=username)
+        count = M.AuditLog.for_user(user).count()
+        q = M.AuditLog.for_user(user)
+        q = q.sort('timestamp', -1)
+        q = q.skip(page * limit)
+        if count > limit:
+            q = q.limit(limit)
+        else:
+            limit = count
+        c.widget = W.audit
+        return dict(
+            entries=q.all(),
+            limit=limit,
+            page=page,
+            count=count,
+            username=username)
 
 class TaskManagerController(object):
 

http://git-wip-us.apache.org/repos/asf/allura/blob/ab49aa3d/Allura/allura/model/auth.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/auth.py b/Allura/allura/model/auth.py
index ed3c4d0..469ae03 100644
--- a/Allura/allura/model/auth.py
+++ b/Allura/allura/model/auth.py
@@ -874,6 +874,10 @@ class AuditLog(object):
             message = message % kwargs
         return cls(project_id=project._id, user_id=user._id, url=url, message=message)
 
+    @classmethod
+    def for_user(cls, user):
+        return cls.query.find(dict(project_id=None, user_id=user._id))
+
 main_orm_session.mapper(AuditLog, audit_log, properties=dict(
     project_id=ForeignIdProperty('Project'),
     project=RelationProperty('Project'),

http://git-wip-us.apache.org/repos/asf/allura/blob/ab49aa3d/Allura/allura/templates/site_admin_users_audit.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/site_admin_users_audit.html b/Allura/allura/templates/site_admin_users_audit.html
new file mode 100644
index 0000000..d4c52fa
--- /dev/null
+++ b/Allura/allura/templates/site_admin_users_audit.html
@@ -0,0 +1,37 @@
+{#-
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+-#}
+
+{% extends 'allura:templates/site_admin.html' %}
+
+{% block content %}
+<div class='grid-19'>
+  <form action='' method='GET'>
+    <div class='grid-7'>
+      <label for='username'>Username:</label>
+      <input name='username' value='{{ username if username else ''}}'>
+    </div>
+    <div class='grid-5'>
+      <input type='submit' value='Show'>
+    </div>
+  </form>
+  {% if entries %}
+    {{ c.widget.display(entries=entries, limit=limit, page=page, count=count) }}
+  {% endif %}
+</div>
+{% endblock %}


[3/4] git commit: [#7524] ticket:613 Don't track activity if neither project or user are provided

Posted by je...@apache.org.
[#7524] ticket:613 Don't track activity if neither project or user are provided


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/368675a6
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/368675a6
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/368675a6

Branch: refs/heads/je/42cc_7524
Commit: 368675a6bc1c42172ea2787465748bfe5c1a6ba2
Parents: 94fa2de
Author: Igor Bondarenko <je...@gmail.com>
Authored: Tue Jul 22 12:33:16 2014 +0300
Committer: Igor Bondarenko <je...@gmail.com>
Committed: Tue Jul 22 12:33:16 2014 +0300

----------------------------------------------------------------------
 Allura/allura/model/auth.py | 2 ++
 1 file changed, 2 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/368675a6/Allura/allura/model/auth.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/auth.py b/Allura/allura/model/auth.py
index cc2d841..567487a 100644
--- a/Allura/allura/model/auth.py
+++ b/Allura/allura/model/auth.py
@@ -873,6 +873,8 @@ class AuditLog(object):
         elif kwargs:
             message = message % kwargs
         pid = project._id if project is not None else None
+        if pid is None and user is None or user.is_anonymous():
+            return
         return cls(project_id=pid, user_id=user._id, url=url, message=message)
 
     @classmethod


[4/4] git commit: [#7524] ticket:613 Add some tests for user account audit

Posted by je...@apache.org.
[#7524] ticket:613 Add some tests for user account audit


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/c03d10d1
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/c03d10d1
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/c03d10d1

Branch: refs/heads/je/42cc_7524
Commit: c03d10d1f119adac9362d1ffe66cca8c7c0eb678
Parents: 368675a
Author: Igor Bondarenko <je...@gmail.com>
Authored: Tue Jul 22 13:31:23 2014 +0300
Committer: Igor Bondarenko <je...@gmail.com>
Committed: Tue Jul 22 13:31:23 2014 +0300

----------------------------------------------------------------------
 Allura/allura/tests/functional/test_auth.py     | 32 +++++++++++++++++---
 .../allura/tests/functional/test_site_admin.py  | 21 ++++++++++++-
 2 files changed, 48 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/c03d10d1/Allura/allura/tests/functional/test_auth.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_auth.py b/Allura/allura/tests/functional/test_auth.py
index 1c41e8a..c79b52e 100644
--- a/Allura/allura/tests/functional/test_auth.py
+++ b/Allura/allura/tests/functional/test_auth.py
@@ -98,8 +98,12 @@ class TestAuth(TestController):
             extra_environ=dict(username='test-admin'))
         r = self.app.get('/auth/preferences/')
         assert 'test@example.com' in r
-        assert_equal(M.User.query.get(username='test-admin').get_pref('email_address'),
-                     'test-admin@users.localhost')
+        user = M.User.query.get(username='test-admin')
+        assert_equal(user.get_pref('email_address'), 'test-admin@users.localhost')
+        log = M.AuditLog.for_user(user).all()
+        assert_equal(len(log), 1)
+        assert_equal(log[0].message, 'New email address: test@example.com')
+        M.AuditLog.query.remove()
 
         # remove test-admin@users.localhost
         r = self.app.post('/auth/preferences/update', params={
@@ -114,8 +118,21 @@ class TestAuth(TestController):
         r = self.app.get('/auth/preferences/')
         assert 'test-admin@users.localhost' not in r
         # preferred address has not changed if email is not verified
-        assert_equal(M.User.query.get(username='test-admin').get_pref('email_address'),
-                     None)
+        user = M.User.query.get(username='test-admin')
+        assert_equal(user.get_pref('email_address'), None)
+        log = M.AuditLog.for_user(user).all()
+        assert_equal(len(log), 1)
+        assert_equal(log[0].message, 'Email address deleted: test-admin@users.localhost')
+        M.AuditLog.query.remove()
+
+        r = self.app.post('/auth/preferences/update', params={
+            'preferences.display_name': 'Admin',
+            'new_addr.addr': ''},
+            extra_environ=dict(username='test-admin'))
+        user = M.User.query.get(username='test-admin')
+        log = M.AuditLog.for_user(user).all()
+        assert_equal(len(log), 1)
+        assert_equal(log[0].message, 'Display Name changed Test Admin => Admin')
 
     @td.with_user_project('test-admin')
     def test_prefs_subscriptions(self):
@@ -737,6 +754,13 @@ To reset your password on %s, please visit the following URL:
         hash_expiry = user.get_tool_data('AuthPasswordReset', 'hash_expiry')
         assert_equal(hash, '')
         assert_equal(hash_expiry, '')
+        log = M.AuditLog.for_user(user).all()
+        assert_equal(len(log), 2)
+        messages = set([l.message for l in log])
+        assert_equal(
+            messages,
+            set(['Password recovery link sent to: test-admin@users.localhost',
+                 'Password changed (through recovery process)']))
 
     @patch('allura.tasks.mail_tasks.sendmail')
     @patch('allura.lib.helpers.gen_message_id')

http://git-wip-us.apache.org/repos/asf/allura/blob/c03d10d1/Allura/allura/tests/functional/test_site_admin.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_site_admin.py b/Allura/allura/tests/functional/test_site_admin.py
index 79ca43b..8dbc2f6 100644
--- a/Allura/allura/tests/functional/test_site_admin.py
+++ b/Allura/allura/tests/functional/test_site_admin.py
@@ -17,8 +17,10 @@
 
 import json
 
-from nose.tools import assert_equal
+from mock import patch
+from nose.tools import assert_equal, assert_in, assert_not_in
 from ming.odm import ThreadLocalORMSession
+from pylons import tmpl_context as c
 
 from allura import model as M
 from allura.tests import TestController
@@ -160,6 +162,23 @@ class TestSiteAdmin(TestController):
             task_name='allura.tests.functional.test_site_admin.test_task'))
         assert json.loads(r.body)['doc'] == 'test_task doc string'
 
+    @patch('allura.model.auth.request')
+    def test_users(self, request):
+        request.url = 'http://host.domain/path/'
+        c.user = M.User.by_username('test-user-1')
+        M.AuditLog.log_user('test activity user 1')
+        M.AuditLog.log_user('test activity user 2', user=M.User.by_username('test-user-2'))
+        r = self.app.get('/nf/admin/users')
+        assert_not_in('test activity', r)
+        r = self.app.get('/nf/admin/users?username=admin1')
+        assert_not_in('test activity', r)
+        r = self.app.get('/nf/admin/users?username=test-user-1')
+        assert_in('test activity user 1', r)
+        assert_not_in('test activity user 2', r)
+        r = self.app.get('/nf/admin/users?username=test-user-2')
+        assert_not_in('test activity user 1', r)
+        assert_in('test activity user 2', r)
+
 
 @task
 def test_task(*args, **kw):


[2/4] git commit: [#7524] ticket:613 Track changes to user accounts

Posted by je...@apache.org.
[#7524] ticket:613 Track changes to user accounts


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/94fa2de7
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/94fa2de7
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/94fa2de7

Branch: refs/heads/je/42cc_7524
Commit: 94fa2de7351bb00cc12b317f0fcfc5502a5a139a
Parents: ab49aa3
Author: Igor Bondarenko <je...@gmail.com>
Authored: Tue Jul 22 10:44:51 2014 +0300
Committer: Igor Bondarenko <je...@gmail.com>
Committed: Tue Jul 22 10:44:51 2014 +0300

----------------------------------------------------------------------
 Allura/allura/controllers/auth.py | 14 ++++++++++++++
 Allura/allura/model/auth.py       |  8 +++++++-
 2 files changed, 21 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/94fa2de7/Allura/allura/controllers/auth.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/auth.py b/Allura/allura/controllers/auth.py
index 4d15a9a..e7ce473 100644
--- a/Allura/allura/controllers/auth.py
+++ b/Allura/allura/controllers/auth.py
@@ -166,6 +166,7 @@ class AuthController(BaseController):
         user = self._validate_hash(hash)
         user.set_password(pw)
         user.set_tool_data('AuthPasswordReset', hash='', hash_expiry='')
+        M.AuditLog.log_user('Password changed (through recovery process)', user=user)
         flash('Password changed')
         redirect('/auth/')
 
@@ -204,6 +205,7 @@ class AuthController(BaseController):
                 message_id=h.gen_message_id(),
                 text=text)
 
+        M.AuditLog.log_user('Password recovery link sent to: %s', email, user=user_record)
         flash('A password reset email has been sent, if the given email address is on record in our system.')
         redirect('/')
 
@@ -237,6 +239,7 @@ class AuthController(BaseController):
         if addr:
             addr.confirmed = True
             flash('Email address confirmed')
+            M.AuditLog.log_user('Email address verified: %s', addr._id)
         else:
             flash('Unknown verification link', 'error')
         redirect('/auth/preferences/')
@@ -409,7 +412,10 @@ class PreferencesController(BaseController):
             if not preferences.get('display_name'):
                 flash("Display Name cannot be empty.", 'error')
                 redirect('.')
+            old = c.user.get_pref('display_name')
             c.user.set_pref('display_name', preferences['display_name'])
+            if old != preferences['display_name']:
+                M.AuditLog.log_user('Display Name changed %s => %s', old, preferences['display_name'])
             for i, (old_a, data) in enumerate(zip(c.user.email_addresses, addr or [])):
                 obj = c.user.address_object(old_a)
                 if data.get('delete') or not obj:
@@ -422,6 +428,7 @@ class PreferencesController(BaseController):
                             # clear it now, a new one will get set below
                             c.user.set_pref('email_address', None)
                             primary_addr = None
+                    M.AuditLog.log_user('Email address deleted: %s', c.user.email_addresses[i])
                     del c.user.email_addresses[i]
                     if obj:
                         obj.delete()
@@ -433,12 +440,18 @@ class PreferencesController(BaseController):
                     em = M.EmailAddress.upsert(new_addr['addr'])
                     em.claimed_by_user_id = c.user._id
                     em.send_verification_link()
+                    M.AuditLog.log_user('New email address: %s', new_addr['addr'])
                     flash('A verification email has been sent.  Please check your email and click to confirm.')
                 else:
                     flash('Email address %s is invalid' % new_addr['addr'], 'error')
             if not primary_addr and not c.user.get_pref('email_address') and c.user.email_addresses:
                 primary_addr = select_new_primary_addr(c.user)
             if primary_addr:
+                if c.user.get_pref('email_address') != primary_addr:
+                    M.AuditLog.log_user(
+                        'Primary email changed: %s => %s',
+                        c.user.get_pref('email_address'),
+                        primary_addr)
                 c.user.set_pref('email_address', primary_addr)
             for k, v in preferences.iteritems():
                 if k == 'results_per_page':
@@ -458,6 +471,7 @@ class PreferencesController(BaseController):
             flash('Incorrect password', 'error')
             redirect('.')
         flash('Password changed')
+        M.AuditLog.log_user('Password changed')
         redirect('.')
 
     @expose()

http://git-wip-us.apache.org/repos/asf/allura/blob/94fa2de7/Allura/allura/model/auth.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/auth.py b/Allura/allura/model/auth.py
index 469ae03..cc2d841 100644
--- a/Allura/allura/model/auth.py
+++ b/Allura/allura/model/auth.py
@@ -872,12 +872,18 @@ class AuditLog(object):
             message = message % args
         elif kwargs:
             message = message % kwargs
-        return cls(project_id=project._id, user_id=user._id, url=url, message=message)
+        pid = project._id if project is not None else None
+        return cls(project_id=pid, user_id=user._id, url=url, message=message)
 
     @classmethod
     def for_user(cls, user):
         return cls.query.find(dict(project_id=None, user_id=user._id))
 
+    @classmethod
+    def log_user(cls, message, *args, **kwargs):
+        kwargs['project'] = None
+        return cls.log(message, *args, **kwargs)
+
 main_orm_session.mapper(AuditLog, audit_log, properties=dict(
     project_id=ForeignIdProperty('Project'),
     project=RelationProperty('Project'),