#4221 Add project connector api endpoint
Merged a year ago by pingou. Opened a year ago by fbo.

file modified
+81

@@ -1963,6 +1963,87 @@ 

      return flask.jsonify({"settings": project.settings, "status": "ok"})

  

  

+ @API.route("/<repo>/connector", methods=["GET"])

+ @API.route("/<namespace>/<repo>/connector", methods=["GET"])

+ @API.route("/fork/<username>/<repo>/connector", methods=["GET"])

+ @API.route("/fork/<username>/<namespace>/<repo>/connector", methods=["GET"])

+ @api_login_required(acls=["modify_project"])

+ @api_method

+ def api_get_project_connector(repo, username=None, namespace=None):

+     """

+     Get project connector

+     ---------------------

+     Allow project owners and admins to retrieve their own connector tokens.

+     Connector tokens are the API tokens and the Web Hook token

+     of the project. Connector tokens make possible for an external

+     application to listen and verify project notifications and act

+     on project via the REST API.

+ 

+     ::

+ 

+         GET /api/0/<repo>/connector

+         GET /api/0/<namespace>/<repo>/connector

+ 

+     ::

+ 

+         GET /api/0/fork/<username>/<repo>/connector

+         GET /api/0/fork/<username>/<namespace>/<repo>/connector

+ 

+     Sample response

+     ^^^^^^^^^^^^^^^

+ 

+     ::

+ 

+         {

+           "connector": {

+               "hook_token": "aaabbbccc",

+               "api_token": [

+                   {'name': 'foo token',

+                    'id': "abcdefoo",

+                    'expired': True}

+                   {'name': 'bar token',

+                    'id': "abcdebar",

+                    'expired': False}

+               ]

+           },

+           "status": "ok"

+         }

+ 

+     """

+     project = get_authorized_api_project(

+         flask.g.session, repo, namespace=namespace

+     )

+     if not project:

+         raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT)

+ 

+     if flask.g.token.project and project != flask.g.token.project:

+         raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK)

+ 

+     authorized_users = [project.user.username]

+     authorized_users.extend(

+         [user.user for user in project.access_users['admin']])

+     if flask.g.fas_user.user not in authorized_users:

+         raise pagure.exceptions.APIError(

+             401, error_code=APIERROR.ENOTHIGHENOUGH)

+ 

+     user_obj = pagure.lib.query.search_user(

+         flask.g.session, username=flask.g.fas_user.user)

+     user_project_tokens = [

+         token for token in user_obj.tokens if token.project_id == project.id]

+ 

+     connector = {

+         'hook_token': project.hook_token,

+         'api_tokens': [

+             {'description': t.description,

+              'id': t.id,

+              'expired': t.expired

+              } for t in user_project_tokens

+         ]

+     }

+ 

+     return flask.jsonify({"connector": connector, "status": "ok"})

+ 

+ 

  def _check_value(value):

      """ Convert the provided value into a boolean, an int or leave it as it.

      """

@@ -4097,5 +4097,164 @@ 

          self.assertEqual(after, before)

  

  

+ class PagureFlaskApiProjectConnectorTests(tests.Modeltests):

+     """ Tests for the flask API of pagure for getting connector of a project

+     """

+ 

+     maxDiff = None

+ 

+     def setUp(self):

+         """ Set up the environnment, ran before every tests. """

+         super(PagureFlaskApiProjectConnectorTests, self).setUp()

+         tests.create_projects(self.session)

+         tests.create_tokens(self.session, project_id=None)

+         tests.create_tokens_acl(

+             self.session, 'aaabbbcccddd', 'modify_project')

+ 

+ 

+     def test_api_get_project_connector_as_owner(self):

+         """ Test accessing api_get_project_connector as project owner. """

+ 

+         project = pagure.lib.query._get_project(self.session, 'test')

+ 

+         # Create witness project Token for pingou user

+         pagure.lib.query.add_token_to_user(

+             self.session,

+             project=project,

+             acls=['pull_request_merge'],

+             username='pingou')

+         ctokens = pagure.lib.query.search_token(

+             self.session, ['pull_request_merge'], user='pingou')

+         self.assertEqual(len(ctokens), 1)

+ 

+         # Call the connector with pingou user token and verify content

+         headers = {'Authorization': 'token aaabbbcccddd'}

+         output = self.app.get('/api/0/test/connector', headers=headers)

+         self.assertEqual(output.status_code, 200)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertEqual(

+             data,

+             {"connector": {

+                 "hook_token": project.hook_token,

+                 "api_tokens": [

+                     {'description': t.description,

+                      'id': t.id,

+                      'expired': False} for t in ctokens]

+             },

+             "status": "ok"

+             }

+         )

+ 

+     def test_api_get_project_connector_as_admin(self):

+         """ Test accessing api_get_project_connector as project admin """

+ 

+         project = pagure.lib.query._get_project(self.session, 'test')

+ 

+         # Set the foo user as test project admin

+         pagure.lib.query.add_user_to_project(

+             self.session, project,

+             new_user='foo',

+             user='pingou',

+             access='admin'

+         )

+         self.session.commit()

+ 

+         # Create modify_project token for foo user

+         pagure.lib.query.add_token_to_user(

+             self.session,

+             project=None,

+             acls=['modify_project'],

+             username='foo')

+         mtoken = pagure.lib.query.search_token(

+             self.session, ['modify_project'], user='foo')[0]

+ 

+         # Create witness project Token for foo user

+         pagure.lib.query.add_token_to_user(

+             self.session,

+             project=project,

+             acls=['pull_request_merge'],

+             username='foo')

+         ctokens = pagure.lib.query.search_token(

+             self.session, ['pull_request_merge'], user='foo')

+         self.assertEqual(len(ctokens), 1)

+ 

+         # Call the connector with foo user token and verify content

+         headers = {'Authorization': 'token %s' % mtoken.id}

+         output = self.app.get('/api/0/test/connector', headers=headers)

+         self.assertEqual(output.status_code, 200)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertEqual(

+             data,

+             {"connector": {

+                 "hook_token": project.hook_token,

+                 "api_tokens": [

+                     {'description': t.description,

+                      'id': t.id,

+                      'expired': False} for t in ctokens]

+             },

+             "status": "ok"

+             }

+         )

+ 

+     def test_api_get_project_connector_as_unauthorized(self):

+         """ Test accessing api_get_project_connector as project admin

+         but with unauthorized token ACL

+         """

+ 

+         project = pagure.lib.query._get_project(self.session, 'test')

+ 

+         # Set the foo user as test project admin

+         pagure.lib.query.add_user_to_project(

+             self.session, project,

+             new_user='foo',

+             user='pingou',

+             access='admin'

+         )

+         self.session.commit()

+ 

+         # Create modify_project token for foo user

+         pagure.lib.query.add_token_to_user(

+             self.session,

+             project=None,

+             acls=['create_project'],

+             username='foo')

+         mtoken = pagure.lib.query.search_token(

+             self.session, ['create_project'], user='foo')[0]

+ 

+         # Call the connector with foo user token and verify unauthorized

+         headers = {'Authorization': 'token %s' % mtoken.id}

+         output = self.app.get('/api/0/test/connector', headers=headers)

+         self.assertEqual(output.status_code, 401)

+ 

+     def test_api_get_project_connector_as_unauthorized_2(self):

+         """ Test accessing api_get_project_connector as project

+         but with unauthorized token ACL

+         """

+ 

+         project = pagure.lib.query._get_project(self.session, 'test')

+ 

+         # Set the foo user as test project admin

+         pagure.lib.query.add_user_to_project(

+             self.session, project,

+             new_user='foo',

+             user='pingou',

+             access='commit'

+         )

+         self.session.commit()

+ 

+         # Create modify_project token for foo user

+         pagure.lib.query.add_token_to_user(

+             self.session,

+             project=None,

+             acls=['modify_project'],

+             username='foo')

+         mtoken = pagure.lib.query.search_token(

+             self.session, ['modify_project'], user='foo')[0]

+ 

+         # Call the connector with foo user token and verify unauthorized

+         headers = {'Authorization': 'token %s' % mtoken.id}

+         output = self.app.get('/api/0/test/connector', headers=headers)

+         self.assertEqual(output.status_code, 401)

+ 

  if __name__ == '__main__':

      unittest.main(verbosity=2)

This endpoint aims to provide a get access to connector
tokens that are:
- project hook token
- project's user (caller) API token

I call them connector data as those tokens are the
mandatory data an application should have to verify project
events received via the Web hooks but also to perform actions
via the API to a given project.

The endpoint is only available to project's owner and admin
users with a 'modify_project' ACL token.

Thanks to this endpoint, a user (or an application) added as
admin on one or more projects, and via its user token, would
be able to read connector data on those projects and then
received events and perform API call on those same projects.

I'm not sure why success wasn't reported back, but the job passed in Jenkins.

1 new commit added

  • Api: project connector endpoint: complete returned data
a year ago

Thanks Neal for your review, I have added the token expiration status to the returned data in that second commit.

The API tokens of the user making the request ?

And the expiration now :)

Should we check the number of tokens returned?

1 new commit added

  • Fix docstring of the connector api endpoint
a year ago

rebased onto dfb30c4

a year ago

Pull-Request has been merged by pingou

a year ago