#4698 Add the GET project/webhook/token endpoint
Merged 4 years ago by pingou. Opened 4 years ago by fbo.
fbo/pagure gettokenendpoint  into  master

file modified
+9 -2
@@ -143,9 +143,12 @@ 

      return flask.request.form or flask.request.get_json() or {}

  

  

- def api_login_required(acls=None):

+ def api_login_required(acls=None, optional=False):

      """ Decorator used to indicate that authentication is required for some

      API endpoint.

+ 

+     :arg acls: A list of access control

+     :arg optional: Only check the API token is valid. Skip the ACL validation.

      """

  

      def decorator(function):
@@ -155,7 +158,7 @@ 

          def decorated_function(*args, **kwargs):

              """ Actually does the job with the arguments provided. """

  

-             response = check_api_acls(acls)

+             response = check_api_acls(acls, optional)

              if response:

                  return response

  
@@ -514,6 +517,9 @@ 

          project.api_modify_project_options

      )

      api_project_block_user_doc = load_doc(project.api_project_block_user)

+     api_get_project_webhook_token_doc = load_doc(

+         project.api_get_project_webhook_token

+     )

  

      issues = []

      if pagure_config.get("ENABLE_TICKETS", True):
@@ -608,6 +614,7 @@ 

              api_get_project_options_doc,

              api_modify_project_options_doc,

              api_project_block_user_doc,

+             api_get_project_webhook_token_doc,

          ],

          issues=issues,

          requests=[

file modified
+56
@@ -2242,6 +2242,62 @@ 

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

  

  

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

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

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

+ @API.route(

+     "/fork/<username>/<namespace>/<repo>/webhook/token", methods=["GET"]

+ )

+ @api_method

+ @api_login_required(acls=[], optional=True)

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

+     """

+     Get project webhook token

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

+     Allow project collaborators to retrieve the project webhook token.

+ 

+     ::

+ 

+         GET /api/0/<repo>/webhook/token

+         GET /api/0/<namespace>/<repo>/webhook/token

+ 

+     ::

+ 

+         GET /api/0/fork/<username>/<repo>/webhook/token

+         GET /api/0/fork/<username>/<namespace>/<repo>/webhook/token

+ 

+     Sample response

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

+ 

+     ::

+ 

+         {

+           "webhook": {

+               "token": "aaabbbccc",

+           },

+           "status": "ok"

+         }

+ 

+     """

+     project = _get_repo(repo, username, namespace)

+     _check_token(project, project_token=False)

+ 

+     authorized_users = [project.user.username]

+     # All collaborators are authorized to read the token

+     for access_type in project.access_users.keys():

+         authorized_users.extend(

+             [user.user for user in project.access_users[access_type]]

+         )

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

+         raise pagure.exceptions.APIError(

+             401, error_code=APIERROR.ENOTHIGHENOUGH

+         )

+ 

+     webhook_token = {"token": project.hook_token}

+ 

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

+ 

+ 

  def _check_value(value):

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

      """

file modified
+3
@@ -68,6 +68,9 @@ 

  def check_api_acls(acls, optional=False):

      """ Checks if the user provided an API token with its request and if

      this token allows the user to access the endpoint desired.

+ 

+     :arg acls: A list of access control

+     :arg optional: Only check the API token is valid. Skip the ACL validation.

      """

      import pagure.api

      import pagure.lib.query

@@ -4327,5 +4327,84 @@ 

          self.assertEqual(output.status_code, 401)

  

  

+ class PagureFlaskApiProjectWebhookTokenTests(tests.Modeltests):

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

+     """

+ 

+     maxDiff = None

+ 

+     def setUp(self):

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

+         super(PagureFlaskApiProjectWebhookTokenTests, self).setUp()

+         tests.create_projects(self.session)

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

+         # Set a default ACL to avoid get all rights set on

+         tests.create_tokens_acl(self.session, "aaabbbcccddd", "issue_assign")

+ 

+     def test_api_get_project_webhook_token_as_owner(self):

+         """ Test accessing webhook token as project owner. """

+ 

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

+ 

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

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

+         output = self.app.get("/api/0/test/webhook/token", headers=headers)

+         self.assertEqual(output.status_code, 200)

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

+         self.assertEqual(

+             data, {"webhook": {"token": project.hook_token}, "status": "ok"}

+         )

+ 

+     def test_api_get_project_webhook_token_as_collaborator(self):

+         """ Test accessing webhook token as project collaborator. """

+ 

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

+ 

+         # Set the foo user as test project collaborator ticket access level

+         pagure.lib.query.add_user_to_project(

+             self.session,

+             project,

+             new_user="foo",

+             user="pingou",

+             access="ticket",

+         )

+         self.session.commit()

+ 

+         # Create token for foo user with a default ACL

+         mtoken = pagure.lib.query.add_token_to_user(

+             self.session,

+             project=None,

+             acls=["issue_assign"],

+             username="foo",

+             expiration_date=datetime.date.today() + datetime.timedelta(days=1),

+         )

+ 

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

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

+         output = self.app.get("/api/0/test/webhook/token", headers=headers)

+         self.assertEqual(output.status_code, 200)

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

+         self.assertEqual(

+             data, {"webhook": {"token": project.hook_token}, "status": "ok"}

+         )

+ 

+     def test_api_get_project_webhook_token_as_not_collaborator(self):

+         """ Test accessing webhook token as not a project collaborator. """

+ 

+         # Create token for foo user with a default ACL

+         mtoken = pagure.lib.query.add_token_to_user(

+             self.session,

+             project=None,

+             acls=["issue_assign"],

+             username="foo",

+             expiration_date=datetime.date.today() + datetime.timedelta(days=1),

+         )

+ 

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

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

+         output = self.app.get("/api/0/test/webhook/token", headers=headers)

+         self.assertEqual(output.status_code, 401)

+ 

+ 

  if __name__ == "__main__":

      unittest.main(verbosity=2)

This endpoint allows a project's collaborator to access the
project webhook token. It is useful in case a collaborator
user want to react to project events via the webhook system
because it will need to validate the webhook payloads.

In particular, third party applications that need
to react to multiple projects events wont need to be configured
with a long list of token (one for each project) to be able to
validate event's payloads. Instead the application will just
need to access the projects' endpoint to figure out webhook
tokens.

1 new commit added

  • Fix black format
4 years ago

pretty please pagure-ci rebuild

4 years ago

hm, let's document what the optional here means as it seems a little bit contradictory with the name of the function

1 new commit added

  • Add a docstring to api_login_required method
4 years ago

rebased onto ae929db147b115460e83d016b6bb99d5ab865f34

4 years ago

The new endpoint needs to be added to the API documentation page (in api/__init__.py)

1 new commit added

  • Add new endpoint doc api_get_project_webhook_token
4 years ago

pretty please pagure-ci rebuild

4 years ago

pretty please pagure-ci rebuild

4 years ago

pretty please pagure-ci rebuild

4 years ago

pretty please pagure-ci rebuild

4 years ago

pretty please pagure-ci rebuild

4 years ago

pretty please pagure-ci rebuild

4 years ago

pretty please pagure-ci rebuild

4 years ago

pretty please pagure-ci rebuild

4 years ago

pretty please pagure-ci rebuild

4 years ago

pretty please pagure-ci rebuild

4 years ago

@jlanda is there an issue with the CI ? I'm not sure to get it correctly. :/

@jlanda is there an issue with the CI ? I'm not sure to get it correctly. :/

yep. we're hitting https://bugs.centos.org/view.php?id=16383 all the time :(

Could you or @pingou rebase the pull request?

rebased onto 62fe6abb276cd4c0f4821f642b29823e57089246

4 years ago

@jlanda, @pingou Me and my team would be happy to provide CI capacity via Zuul for the Pagure project. Our Zuul instance is already configured to react to pagure.io's Pull Requests. We can help to bootstrap jobs if needed. Let us know what do you think.

@fbo How difficult would it be to get started with it for pagure.io? Can we have CI that reacts on both PRs and regular pushes?

@ngompa, I think that quite easy. Yes Zuul can react to PR events but also to refs update on Git repositories so we could add jobs on both kind of events.

The process to attach the pagure repository to the Zuul instance of softwarefactory-project.io is:
First:
- Add the webhook target to the url https://softwarefactory-project.io/zuul/api/connection/pagure.io/payload
- Optional, add the zuul user in the "commit" group of the project. This is needed only if you want to enable the gating.
->https://fedoraproject.org/wiki/Zuul-based-ci#Configure_the_repository_for_Zuul

Next the repository must be defined in the Zuul configuration as explained here https://fedoraproject.org/wiki/Zuul-based-ci#Add_the_repository_into_the_Zuul_configuration

Finally, for the pagure project, we could attach the predefined tox job https://fedora.softwarefactory-project.io/zuul/job/tox-py37 and develop custom if needed.

@jlanda, @pingou Me and my team would be happy to provide CI capacity via Zuul for the Pagure project. Our Zuul instance is already configured to react to pagure.io's Pull Requests. We can help to bootstrap jobs if needed. Let us know what do you think.

Sounds great!!!

@fbo Could you please rebase this?

rebased onto e866ab8a7b5951c766984f02a9f7b4697f042e08

4 years ago
05:02:32  Failed tests:
05:02:32  FAILED test: py3-test_pagure_flask_api_project

1 new commit added

  • Fix missing expiration_date for token arg in tests
4 years ago

You're nearly there!

08:40:37  Failed tests:
08:40:37  FAILED test: py3-test_style

1 new commit added

  • Fix black format
4 years ago

@fbo Could you squash all the commits into a single one? It seems like this is really a single change...

LGTM befiore a new ci glitch

Yeah, +1 to squash and pray to jenkins

rebased onto c27154a55ca38f71389e360fcb0448aa8623feef

4 years ago

I believe this is the "foo" user not pingou

One tiny comment and good to go :)

rebased onto 003b24f0856941f365bb1ab595017ae4af3371e7

4 years ago

Good catch. Fix done.

@fbo Please rebase the PR so it can be merged. :)

rebased onto f483d70

4 years ago

Pull-Request has been merged by pingou

4 years ago
Metadata
Flags
jenkins
error
Build #3139 aborted (commit: f483d70e)
4 years ago
jenkins
success (100%)
Build #3140 successful (commit: f483d70e)
4 years ago
jenkins
success (100%)
Build #3125 successful (commit: c27154a5)
4 years ago
jenkins
success (100%)
Build #3124 successful (commit: 884311c1)
4 years ago
jenkins
failure
Build #3123 failed (commit: 7ad75b88)
4 years ago
jenkins
failure
Build #3121 failed (commit: e2578700)
4 years ago
jenkins
error
Build #3099 aborted (commit: 63d392e6)
4 years ago
jenkins
success (100%)
Build #3103 successful (commit: 63d392e6)
4 years ago
jenkins
error
Build #3086 aborted (commit: a17f7db2)
4 years ago
jenkins
error
Build #3085 aborted (commit: a17f7db2)
4 years ago
jenkins
error
Build #3084 aborted (commit: a17f7db2)
4 years ago
jenkins
error
Build #3082 aborted (commit: a17f7db2)
4 years ago
jenkins
error
Build #3080 aborted (commit: a17f7db2)
4 years ago
jenkins
error
Build #3078 aborted (commit: a17f7db2)
4 years ago
jenkins
error
Build #3076 aborted (commit: a17f7db2)
4 years ago
jenkins
error
Build #3074 aborted (commit: a17f7db2)
4 years ago
jenkins
error
Build #3072 aborted (commit: a17f7db2)
4 years ago
jenkins
error
Build #3066 aborted (commit: a17f7db2)
4 years ago
jenkins
success (100%)
Build #3068 successful (commit: 2d37a001)
4 years ago
jenkins
success (100%)
Build #3047 successful (commit: 2caa6565)
4 years ago
jenkins
failure
Build #3046 failed (commit: 2caa6565)
4 years ago
jenkins
failure
Build #3045 failed (commit: b30e56ed)
4 years ago