From 5a55feb7d449b97b638d8a16d807b7ef52a63ea5 Mon Sep 17 00:00:00 2001
From: Matt Prahl <mprahl@redhat.com>
Date: Apr 13 2017 13:38:08 +0000
Subject: [PATCH 1/4] Remove usage of 'assert_called_once' in test because pinned version of mock doesn't support it


---

diff --git a/tests/test_pagure_lib.py b/tests/test_pagure_lib.py
index d816823..c4e3b59 100644
--- a/tests/test_pagure_lib.py
+++ b/tests/test_pagure_lib.py
@@ -1416,7 +1416,7 @@ class PagureLibtests(tests.Modeltests):
             user='pingou',
         )
         self.assertEqual(msg, 'Edited successfully settings of repo: test2')
-        mock_log.assert_called_once()
+        self.assertEqual(mock_log.call_count, 1)
         args = mock_log.call_args
         self.assertEqual(len(args), 2)
         self.assertEqual(args[0][0].fullname, 'test2')

From 1a51d985c22f55a7c6d223b5a83a99242c9c7ded Mon Sep 17 00:00:00 2001
From: Matt Prahl <mprahl@redhat.com>
Date: Apr 13 2017 13:38:11 +0000
Subject: [PATCH 2/4] Add group information API that shows group members


---

diff --git a/pagure/api/__init__.py b/pagure/api/__init__.py
index 045586c..d349944 100644
--- a/pagure/api/__init__.py
+++ b/pagure/api/__init__.py
@@ -84,6 +84,7 @@ class APIERROR(enum.Enum):
     EINVALIDISSUEFIELD_LINK = 'Invalid custom field submitted, the value '\
         'is not a link'
     EINVALIDPRIORITY = 'Invalid priority submitted'
+    ENOGROUP = 'Group not found'
 
 
 def check_api_acls(acls, optional=False):
@@ -212,6 +213,7 @@ if pagure.APP.config.get('ENABLE_TICKETS', True):
 from pagure.api import fork  # noqa
 from pagure.api import project  # noqa
 from pagure.api import user  # noqa
+from pagure.api import group  # noqa
 
 if pagure.APP.config.get('PAGURE_CI_SERVICES', False):
     from pagure.api.ci import jenkins  # noqa
@@ -480,6 +482,8 @@ def api():
     api_view_user_activity_date_doc = load_doc(
         user.api_view_user_activity_date)
 
+    api_view_group_doc = load_doc(group.api_view_group)
+
     if pagure.APP.config.get('ENABLE_TICKETS', True):
         api_project_tags_doc = load_doc(api_project_tags)
     api_groups_doc = load_doc(api_groups)
@@ -519,6 +523,9 @@ def api():
             api_view_user_activity_stats_doc,
             api_view_user_activity_date_doc,
         ],
+        groups=[
+            api_view_group_doc
+        ],
         ci=ci_doc,
         extras=extras,
     )
diff --git a/pagure/api/group.py b/pagure/api/group.py
new file mode 100644
index 0000000..3e30ca9
--- /dev/null
+++ b/pagure/api/group.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+
+"""
+ (c) 2017 - Copyright Red Hat Inc
+
+ Authors:
+   Matt Prahl <mprahl@redhat.com>
+
+"""
+
+import flask
+
+import pagure
+import pagure.exceptions
+import pagure.lib
+from pagure import SESSION
+from pagure.api import API, api_method, APIERROR
+
+
+@API.route('/group/<group>')
+@api_method
+def api_view_group(group):
+    """
+    Group information
+    -----------------
+    Use this endpoint to retrieve information about a specific group.
+
+    ::
+
+        GET /api/0/group/<group>
+
+    ::
+
+        GET /api/0/group/some_group_name
+
+    Sample response
+    ^^^^^^^^^^^^^^^
+
+    ::
+
+        {
+          "creator": {
+            "default_email": "user1@example.com", 
+            "emails": [
+              "user1@example.com"
+            ], 
+            "fullname": "User1", 
+            "name": "user1"
+          }, 
+          "date_created": "1492011511", 
+          "description": "Some Group", 
+          "display_name": "Some Group", 
+          "group_type": "user", 
+          "members": [
+            "user1", 
+            "user2"
+          ], 
+          "name": "some_group_name"
+        }
+    """
+    group = pagure.lib.search_groups(SESSION, group_name=group)
+    if not group:
+        raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOGROUP)
+
+    jsonout = flask.jsonify(group.to_json())
+    jsonout.status_code = 200
+    return jsonout
diff --git a/pagure/lib/model.py b/pagure/lib/model.py
index 441e7df..3bbf258 100644
--- a/pagure/lib/model.py
+++ b/pagure/lib/model.py
@@ -1978,6 +1978,7 @@ class PagureGroup(BASE):
             'group_type': self.group_type,
             'creator': self.creator.to_json(public=public),
             'date_created': self.created.strftime('%s'),
+            'members': [user.username for user in self.users]
         }
 
         return output
diff --git a/pagure/templates/api.html b/pagure/templates/api.html
index b34c151..d14e235 100644
--- a/pagure/templates/api.html
+++ b/pagure/templates/api.html
@@ -76,6 +76,18 @@
         {% endfor %}
         </div>
 
+        <h2>
+            Groups
+            <a name="groups" title="Permalink to this headline" href="#groups">
+              <span class="oi" data-glyph="link-intact"></span>
+            </a>
+        </h2>
+        <div class="accordion">
+        {% for html in groups %}
+        {{ html | InsertDiv | safe }}
+        {% endfor %}
+        </div>
+
         {% if config.get('PAGURE_CI_SERVICES') %}
         <h2>
             Continous Integration Services
diff --git a/tests/test_pagure_flask_api_group.py b/tests/test_pagure_flask_api_group.py
new file mode 100644
index 0000000..1041372
--- /dev/null
+++ b/tests/test_pagure_flask_api_group.py
@@ -0,0 +1,140 @@
+# -*- coding: utf-8 -*-
+
+"""
+ (c) 2017 - Copyright Red Hat Inc
+
+ Authors:
+   Matt Prahl <mprahl@redhat.com>
+
+"""
+
+__requires__ = ['SQLAlchemy >= 0.8']
+import unittest
+import sys
+import os
+import json
+
+sys.path.insert(0, os.path.join(os.path.dirname(
+    os.path.abspath(__file__)), '..'))
+
+import pagure.api
+import pagure.lib
+import tests
+
+
+class PagureFlaskApiGroupTests(tests.Modeltests):
+    """ Tests for the flask API of pagure for issue """
+
+    def setUp(self):
+        """ Set up the environnment, ran before every tests. """
+        super(PagureFlaskApiGroupTests, self).setUp()
+
+        pagure.APP.config['TESTING'] = True
+        pagure.SESSION = self.session
+        pagure.api.SESSION = self.session
+        pagure.api.group.SESSION = self.session
+        pagure.api.user.SESSION = self.session
+        pagure.lib.SESSION = self.session
+
+        pagure.APP.config['REQUESTS_FOLDER'] = None
+
+        msg = pagure.lib.add_group(
+            self.session,
+            group_name='some_group',
+            display_name='Some Group',
+            description=None,
+            group_type='bar',
+            user='pingou',
+            is_admin=False,
+            blacklist=[],
+        )
+        self.session.commit()
+
+        self.app = pagure.APP.test_client()
+
+    def test_api_view_group(self):
+        """
+            Test the api_view_group method of the flask api
+            The tested group has one member.
+        """
+        output = self.app.get("/api/0/group/some_group")
+        self.assertEqual(output.status_code, 200)
+        exp = {
+            "display_name": "Some Group",
+            "description": None,
+            "creator": {
+                "fullname": "PY C",
+                "default_email": "bar@pingou.com",
+                "emails": [
+                    "bar@pingou.com",
+                    "foo@pingou.com"
+                ],
+                "name": "pingou"
+            },
+            "members": ["pingou"],
+            "date_created": "1492020239",
+            "group_type": "user",
+            "name": "some_group"
+        }
+        data = json.loads(output.data)
+        data['date_created'] = '1492020239'
+        self.assertDictEqual(data, exp)
+
+    def test_api_view_group_two_members(self):
+        """
+            Test the api_view_group method of the flask api
+            The tested group has two members.
+        """
+        user = pagure.lib.model.User(
+            user='mprahl',
+            fullname='Matt Prahl',
+            password='foo',
+            default_email='mprahl@redhat.com',
+        )
+        self.session.add(user)
+        self.session.commit()
+        group = pagure.lib.search_groups(self.session, group_name='some_group')
+        result = pagure.lib.add_user_to_group(
+            self.session, user.username, group, user.username, True)
+        self.session.commit()
+        output = self.app.get("/api/0/group/some_group")
+        self.assertEqual(output.status_code, 200)
+        exp = {
+            "display_name": "Some Group",
+            "description": None,
+            "creator": {
+                "fullname": "PY C",
+                "default_email": "bar@pingou.com",
+                "emails": [
+                    "bar@pingou.com",
+                    "foo@pingou.com"
+                ],
+                "name": "pingou"
+            },
+            "members": ["pingou", "mprahl"],
+            "date_created": "1492020239",
+            "group_type": "user",
+            "name": "some_group"
+        }
+        self.maxDiff = None
+        data = json.loads(output.data)
+        data['date_created'] = '1492020239'
+        from pprint import pprint
+        pprint(data)
+        self.assertDictEqual(data, exp)
+
+    def test_api_view_group_no_group_error(self):
+        """
+            Test the api_view_group method of the flask api
+            The tested group has one member.
+        """
+        output = self.app.get("/api/0/group/some_group3")
+        self.assertEqual(output.status_code, 404)
+        data = json.loads(output.data)
+        self.assertEqual(data['error'], 'Group not found')
+        self.assertEqual(data['error_code'], 'ENOGROUP')
+
+if __name__ == "__main__":
+    SUITE = unittest.TestLoader().loadTestsFromTestCase(
+        PagureFlaskApiGroupTests)
+    unittest.TextTestRunner(verbosity=2).run(SUITE)

From 802ab83ec0e1f31c926f87180359e35e9d9e64dc Mon Sep 17 00:00:00 2001
From: Matt Prahl <mprahl@redhat.com>
Date: Apr 13 2017 13:38:13 +0000
Subject: [PATCH 3/4] Move api_groups function to pagure.api.group


---

diff --git a/pagure/api/__init__.py b/pagure/api/__init__.py
index d349944..3c542f3 100644
--- a/pagure/api/__init__.py
+++ b/pagure/api/__init__.py
@@ -359,55 +359,6 @@ def api_project_tags(repo, username=None):
     )
 
 
-@API.route('/groups/')
-@API.route('/groups')
-def api_groups():
-    '''
-    List groups
-    -----------
-    Retrieve groups on this Pagure instance.
-    This can then be used as input for autocompletion in some forms/fields.
-
-    ::
-
-        GET /api/0/groups
-
-    Parameters
-    ^^^^^^^^^^
-
-    +---------------+----------+---------------+--------------------------+
-    | Key           | Type     | Optionality   | Description              |
-    +===============+==========+===============+==========================+
-    | ``pattern``   | string   | Optional      | | Filters the starting   |
-    |               |          |               |   letters of the group   |
-    |               |          |               |   names                  |
-    +---------------+----------+---------------+--------------------------+
-
-    Sample response
-    ^^^^^^^^^^^^^^^
-
-    ::
-
-        {
-          "total_groups": 2,
-          "groups": ["group1", "group2"]
-        }
-
-    '''
-    pattern = flask.request.args.get('pattern', None)
-    if pattern is not None and not pattern.endswith('*'):
-        pattern += '*'
-
-    groups = pagure.lib.search_groups(SESSION, pattern=pattern)
-
-    return flask.jsonify(
-        {
-            'total_groups': len(groups),
-            'groups': [group.group_name for group in groups]
-        }
-    )
-
-
 @API.route('/error_codes/')
 @API.route('/error_codes')
 def api_error_codes():
@@ -483,10 +434,10 @@ def api():
         user.api_view_user_activity_date)
 
     api_view_group_doc = load_doc(group.api_view_group)
+    api_groups_doc = load_doc(group.api_groups)
 
     if pagure.APP.config.get('ENABLE_TICKETS', True):
         api_project_tags_doc = load_doc(api_project_tags)
-    api_groups_doc = load_doc(api_groups)
     api_error_codes_doc = load_doc(api_error_codes)
 
     extras = [
@@ -519,11 +470,11 @@ def api():
         users=[
             api_users_doc,
             api_view_user_doc,
-            api_groups_doc,
             api_view_user_activity_stats_doc,
             api_view_user_activity_date_doc,
         ],
         groups=[
+            api_groups_doc,
             api_view_group_doc
         ],
         ci=ci_doc,
diff --git a/pagure/api/group.py b/pagure/api/group.py
index 3e30ca9..57b7752 100644
--- a/pagure/api/group.py
+++ b/pagure/api/group.py
@@ -4,6 +4,7 @@
  (c) 2017 - Copyright Red Hat Inc
 
  Authors:
+   Pierre-Yves Chibon <pingou@pingoured.fr>
    Matt Prahl <mprahl@redhat.com>
 
 """
@@ -17,6 +18,55 @@ from pagure import SESSION
 from pagure.api import API, api_method, APIERROR
 
 
+@API.route('/groups/')
+@API.route('/groups')
+def api_groups():
+    '''
+    List groups
+    -----------
+    Retrieve groups on this Pagure instance.
+    This can then be used as input for autocompletion in some forms/fields.
+
+    ::
+
+        GET /api/0/groups
+
+    Parameters
+    ^^^^^^^^^^
+
+    +---------------+----------+---------------+--------------------------+
+    | Key           | Type     | Optionality   | Description              |
+    +===============+==========+===============+==========================+
+    | ``pattern``   | string   | Optional      | | Filters the starting   |
+    |               |          |               |   letters of the group   |
+    |               |          |               |   names                  |
+    +---------------+----------+---------------+--------------------------+
+
+    Sample response
+    ^^^^^^^^^^^^^^^
+
+    ::
+
+        {
+          "total_groups": 2,
+          "groups": ["group1", "group2"]
+        }
+
+    '''
+    pattern = flask.request.args.get('pattern', None)
+    if pattern is not None and not pattern.endswith('*'):
+        pattern += '*'
+
+    groups = pagure.lib.search_groups(SESSION, pattern=pattern)
+
+    return flask.jsonify(
+        {
+            'total_groups': len(groups),
+            'groups': [group.group_name for group in groups]
+        }
+    )
+
+
 @API.route('/group/<group>')
 @api_method
 def api_view_group(group):
diff --git a/tests/test_pagure_flask_api.py b/tests/test_pagure_flask_api.py
index c912d38..0b198cd 100644
--- a/tests/test_pagure_flask_api.py
+++ b/tests/test_pagure_flask_api.py
@@ -109,41 +109,6 @@ class PagureFlaskApitests(tests.Modeltests):
         self.assertEqual(data['tags'], [])
         self.assertEqual(data['total_tags'], 0)
 
-    def test_api_groups(self):
-        """ Test the api_groups function.  """
-
-        # Add a couple of groups so that we can list them
-        item = pagure.lib.model.PagureGroup(
-            group_name='group1',
-            group_type='user',
-            display_name='User group',
-            user_id=1,  # pingou
-        )
-        self.session.add(item)
-
-        item = pagure.lib.model.PagureGroup(
-            group_name='rel-eng',
-            group_type='user',
-            display_name='Release engineering group',
-            user_id=1,  # pingou
-        )
-        self.session.add(item)
-        self.session.commit()
-
-        output = self.app.get('/api/0/groups')
-        self.assertEqual(output.status_code, 200)
-        data = json.loads(output.data)
-        self.assertEqual(data['groups'], ['group1', 'rel-eng'])
-        self.assertEqual(sorted(data.keys()), ['groups', 'total_groups'])
-        self.assertEqual(data['total_groups'], 2)
-
-        output = self.app.get('/api/0/groups?pattern=re')
-        self.assertEqual(output.status_code, 200)
-        data = json.loads(output.data)
-        self.assertEqual(data['groups'], ['rel-eng'])
-        self.assertEqual(sorted(data.keys()), ['groups', 'total_groups'])
-        self.assertEqual(data['total_groups'], 1)
-
 
 if __name__ == '__main__':
     SUITE = unittest.TestLoader().loadTestsFromTestCase(PagureFlaskApitests)
diff --git a/tests/test_pagure_flask_api_group.py b/tests/test_pagure_flask_api_group.py
index 1041372..896e780 100644
--- a/tests/test_pagure_flask_api_group.py
+++ b/tests/test_pagure_flask_api_group.py
@@ -52,6 +52,41 @@ class PagureFlaskApiGroupTests(tests.Modeltests):
 
         self.app = pagure.APP.test_client()
 
+    def test_api_groups(self):
+        """ Test the api_groups function.  """
+
+        # Add a couple of groups so that we can list them
+        item = pagure.lib.model.PagureGroup(
+            group_name='group1',
+            group_type='user',
+            display_name='User group',
+            user_id=1,  # pingou
+        )
+        self.session.add(item)
+
+        item = pagure.lib.model.PagureGroup(
+            group_name='rel-eng',
+            group_type='user',
+            display_name='Release engineering group',
+            user_id=1,  # pingou
+        )
+        self.session.add(item)
+        self.session.commit()
+
+        output = self.app.get('/api/0/groups')
+        self.assertEqual(output.status_code, 200)
+        data = json.loads(output.data)
+        self.assertEqual(data['groups'], ['some_group', 'group1', 'rel-eng'])
+        self.assertEqual(sorted(data.keys()), ['groups', 'total_groups'])
+        self.assertEqual(data['total_groups'], 3)
+
+        output = self.app.get('/api/0/groups?pattern=re')
+        self.assertEqual(output.status_code, 200)
+        data = json.loads(output.data)
+        self.assertEqual(data['groups'], ['rel-eng'])
+        self.assertEqual(sorted(data.keys()), ['groups', 'total_groups'])
+        self.assertEqual(data['total_groups'], 1)
+
     def test_api_view_group(self):
         """
             Test the api_view_group method of the flask api

From 9264b1af733f59dbad93a5d9aec8f1bd90bd1ba5 Mon Sep 17 00:00:00 2001
From: Matt Prahl <mprahl@redhat.com>
Date: Apr 13 2017 13:38:16 +0000
Subject: [PATCH 4/4] Add optional authentication to the groups API


---

diff --git a/pagure/api/group.py b/pagure/api/group.py
index 57b7752..78ab1b2 100644
--- a/pagure/api/group.py
+++ b/pagure/api/group.py
@@ -15,7 +15,7 @@ import pagure
 import pagure.exceptions
 import pagure.lib
 from pagure import SESSION
-from pagure.api import API, api_method, APIERROR
+from pagure.api import API, APIERROR, api_method, api_login_optional
 
 
 @API.route('/groups/')
@@ -68,6 +68,7 @@ def api_groups():
 
 
 @API.route('/group/<group>')
+@api_login_optional()
 @api_method
 def api_view_group(group):
     """
@@ -112,6 +113,7 @@ def api_view_group(group):
     if not group:
         raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOGROUP)
 
-    jsonout = flask.jsonify(group.to_json())
+    jsonout = flask.jsonify(group.to_json(
+        public=(not pagure.api_authenticated())))
     jsonout.status_code = 200
     return jsonout
diff --git a/tests/test_pagure_flask_api_group.py b/tests/test_pagure_flask_api_group.py
index 896e780..8d82405 100644
--- a/tests/test_pagure_flask_api_group.py
+++ b/tests/test_pagure_flask_api_group.py
@@ -87,12 +87,14 @@ class PagureFlaskApiGroupTests(tests.Modeltests):
         self.assertEqual(sorted(data.keys()), ['groups', 'total_groups'])
         self.assertEqual(data['total_groups'], 1)
 
-    def test_api_view_group(self):
+    def test_api_view_group_authenticated(self):
         """
-            Test the api_view_group method of the flask api
-            The tested group has one member.
+            Test the api_view_group method of the flask api with an
+            authenticated user. The tested group has one member.
         """
-        output = self.app.get("/api/0/group/some_group")
+        tests.create_tokens(self.session)
+        headers = {'Authorization': 'token aaabbbcccddd'}
+        output = self.app.get('/api/0/group/some_group', headers=headers)
         self.assertEqual(output.status_code, 200)
         exp = {
             "display_name": "Some Group",
@@ -115,10 +117,33 @@ class PagureFlaskApiGroupTests(tests.Modeltests):
         data['date_created'] = '1492020239'
         self.assertDictEqual(data, exp)
 
-    def test_api_view_group_two_members(self):
+    def test_api_view_group_unauthenticated(self):
         """
-            Test the api_view_group method of the flask api
-            The tested group has two members.
+            Test the api_view_group method of the flask api with an
+            unauthenticated user. The tested group has one member.
+        """
+        output = self.app.get('/api/0/group/some_group')
+        self.assertEqual(output.status_code, 200)
+        exp = {
+            "display_name": "Some Group",
+            "description": None,
+            "creator": {
+                "fullname": "PY C",
+                "name": "pingou"
+            },
+            "members": ["pingou"],
+            "date_created": "1492020239",
+            "group_type": "user",
+            "name": "some_group"
+        }
+        data = json.loads(output.data)
+        data['date_created'] = '1492020239'
+        self.assertDictEqual(data, exp)
+
+    def test_api_view_group_two_members_authenticated(self):
+        """
+            Test the api_view_group method of the flask api with an
+            authenticated user. The tested group has two members.
         """
         user = pagure.lib.model.User(
             user='mprahl',
@@ -132,7 +157,10 @@ class PagureFlaskApiGroupTests(tests.Modeltests):
         result = pagure.lib.add_user_to_group(
             self.session, user.username, group, user.username, True)
         self.session.commit()
-        output = self.app.get("/api/0/group/some_group")
+
+        tests.create_tokens(self.session)
+        headers = {'Authorization': 'token aaabbbcccddd'}
+        output = self.app.get('/api/0/group/some_group', headers=headers)
         self.assertEqual(output.status_code, 200)
         exp = {
             "display_name": "Some Group",

