def _check_private_issue_access(issue): """Check if user can access issue. Must be repo commiter or author to see private issues. :param issue: issue object :raises pagure.exceptions.APIError: when access denied """ if (issue.private and not is_repo_committer(issue.project) and (not api_authenticated() or not issue.user.user == flask.g.fas_user.username)): raise pagure.exceptions.APIError(403, error_code=APIERROR.EISSUENOTALLOWED)
def _check_token(repo, project_token=True): """Check if token is valid for the repo :param repo: repository name :param project_token: set True when project token is required, otherwise any token can be used :raises pagure.exceptions.APIError: when token is not valid for repo """ if api_authenticated(): # if there is a project associated with the token, check it # if there is no project associated, check if it is required if (flask.g.token.project is not None and repo != flask.g.token.project) \ or (flask.g.token.project is None and project_token): raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK)
def api_change_status_issue(repo, issueid, username=None): """ Change issue status ------------------- Change the status of an issue. :: POST /api/0/<repo>/issue/<issue id>/status :: POST /api/0/fork/<username>/<repo>/issue/<issue id>/status Input ^^^^^ +-------------+---------+--------------+------------------------------+ | Key | Type | Optionality | Description | +=============+=========+==============+==============================+ | ``status`` | string | Mandatory | The new status of the issue | +-------------+---------+--------------+------------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "message": "Successfully edited issue #1" } """ repo = pagure.lib.get_project(SESSION, repo, user=username) output = {} if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('issue_tracker', True): raise pagure.exceptions.APIError(404, error_code=APIERROR.ETRACKERDISABLED) if repo != flask.g.token.project: raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOISSUE) if issue.private and not is_repo_admin(repo) \ and (not api_authenticated() or not issue.user.user == flask.g.fas_user.username): raise pagure.exceptions.APIError(403, error_code=APIERROR.EISSUENOTALLOWED) status = pagure.lib.get_issue_statuses(SESSION) form = pagure.forms.StatusForm(status=status, csrf_enabled=False) if form.validate_on_submit(): new_status = form.status.data try: # Update status message = pagure.lib.edit_issue( SESSION, issue=issue, status=new_status, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() if message: output['message'] = message else: output['message'] = 'No changes' except pagure.exceptions.PagureException, err: raise pagure.exceptions.APIError(400, error_code=APIERROR.ENOCODE, error=str(err)) except SQLAlchemyError, err: # pragma: no cover SESSION.rollback() raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)
def api_view_issue_comment(repo, issueid, commentid, username=None): """ Comment of an issue -------------------- Retrieve a specific comment of an issue. :: GET /api/0/<repo>/issue/<issue id>/comment/<comment id> :: GET /api/0/fork/<username>/<repo>/issue/<issue id>/comment/<comment id> The identifier provided can be either the unique identifier or the regular identifier used in the UI (for example ``24`` in ``/forks/user/test/issue/24``) Sample response ^^^^^^^^^^^^^^^ :: { "avatar_url": "https://seccdn.libravatar.org/avatar/...", "comment": "9", "comment_date": "2015-07-01 15:08", "date_created": "1435756127", "id": 464, "parent": null, "user": { "fullname": "P.-Y.C.", "name": "pingou" } } """ repo = pagure.lib.get_project(SESSION, repo, user=username) if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('issue_tracker', True): raise pagure.exceptions.APIError(404, error_code=APIERROR.ETRACKERDISABLED) issue_id = issue_uid = None try: issue_id = int(issueid) except: issue_uid = issueid issue = pagure.lib.search_issues(SESSION, repo, issueid=issue_id, issueuid=issue_uid) if issue is None or issue.project != repo: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOISSUE) if api_authenticated(): if repo != flask.g.token.project: raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) if issue.private and not is_repo_admin(issue.project) \ and (not api_authenticated() or not issue.user.user == flask.g.fas_user.username): raise pagure.exceptions.APIError(403, error_code=APIERROR.EISSUENOTALLOWED) comment = pagure.lib.get_issue_comment(SESSION, issue.uid, commentid) if not comment: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOCOMMENT) output = comment.to_json(public=True) output['avatar_url'] = pagure.lib.avatar_url(comment.user.user, size=16) output['comment_date'] = comment.date_created.strftime('%Y-%m-%d %H:%M') jsonout = flask.jsonify(output) return jsonout
def api_view_issue(repo, issueid, username=None): """ Issue information ----------------- Retrieve information of a specific issue. :: GET /api/0/<repo>/issue/<issue id> :: GET /api/0/fork/<username>/<repo>/issue/<issue id> The identifier provided can be either the unique identifier or the regular identifier used in the UI (for example ``24`` in ``/forks/user/test/issue/24``) Sample response ^^^^^^^^^^^^^^^ :: { "assignee": null, "blocks": [], "comments": [], "content": "This issue needs attention", "date_created": "1431414800", "depends": [], "id": 1, "private": false, "status": "Open", "tags": [], "title": "test issue", "user": { "fullname": "PY C", "name": "pingou" } } """ comments = flask.request.args.get('comments', True) if str(comments).lower() in ['0', 'False']: comments = False repo = pagure.lib.get_project(SESSION, repo, user=username) if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('issue_tracker', True): raise pagure.exceptions.APIError(404, error_code=APIERROR.ETRACKERDISABLED) issue_id = issue_uid = None try: issue_id = int(issueid) except: issue_uid = issueid issue = pagure.lib.search_issues(SESSION, repo, issueid=issue_id, issueuid=issue_uid) if issue is None or issue.project != repo: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOISSUE) if api_authenticated(): if repo != flask.g.token.project: raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) if issue.private and not is_repo_admin(repo) \ and (not api_authenticated() or not issue.user.user == flask.g.fas_user.username): raise pagure.exceptions.APIError(403, error_code=APIERROR.EISSUENOTALLOWED) jsonout = flask.jsonify(issue.to_json(public=True, with_comments=comments)) return jsonout
def api_view_issues(repo, username=None): """ List project's issues --------------------- List issues of a project. :: GET /api/0/<repo>/issues :: GET /api/0/fork/<username>/<repo>/issues Parameters ^^^^^^^^^^ +---------------+---------+--------------+---------------------------+ | Key | Type | Optionality | Description | +===============+=========+==============+===========================+ | ``status`` | string | Optional | | Filters the status of | | | | | issues. Default: | | | | | ``Open`` | +---------------+---------+--------------+---------------------------+ | ``tags`` | string | Optional | | A list of tags you | | | | | wish to filter. If | | | | | you want to filter | | | | | for issues not having | | | | | a tag, add an | | | | | exclamation mark in | | | | | front of it | +---------------+---------+--------------+---------------------------+ | ``assignee`` | string | Optional | | Filter the issues | | | | | by assignee | +---------------+---------+--------------+---------------------------+ | ``author`` | string | Optional | | Filter the issues | | | | | by creator | +---------------+---------+--------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "args": { "assignee": null, "author": null, "status": "Closed", "tags": [ "0.1" ] }, "total_issues": 1, "issues": [ { "assignee": null, "blocks": [], "comments": [], "content": "asd", "date_created": "1427442217", "depends": [], "id": 4, "private": false, "status": "Fixed", "tags": [ "0.1" ], "title": "bug", "user": { "fullname": "PY.C", "name": "pingou" } } ] } """ repo = pagure.lib.get_project(SESSION, repo, user=username) if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('issue_tracker', True): raise pagure.exceptions.APIError(404, error_code=APIERROR.ETRACKERDISABLED) status = flask.request.args.get('status', None) tags = flask.request.args.getlist('tags') tags = [tag.strip() for tag in tags if tag.strip()] assignee = flask.request.args.get('assignee', None) author = flask.request.args.get('author', None) # Hide private tickets private = False # If user is authenticated, show him/her his/her private tickets if api_authenticated(): if repo != flask.g.token.project: raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) private = flask.g.fas_user.username # If user is repo admin, show all tickets included the private ones if is_repo_admin(repo): private = None if status is not None: if status.lower() == 'closed': issues = pagure.lib.search_issues( SESSION, repo, closed=True, tags=tags, assignee=assignee, author=author, private=private, ) else: issues = pagure.lib.search_issues( SESSION, repo, status=status, tags=tags, assignee=assignee, author=author, private=private, ) else: issues = pagure.lib.search_issues(SESSION, repo, status='Open', tags=tags, assignee=assignee, author=author, private=private) jsonout = flask.jsonify({ 'total_issues': len(issues), 'issues': [issue.to_json(public=True) for issue in issues], 'args': { 'status': status, 'tags': tags, 'assignee': assignee, 'author': author, } }) return jsonout
def api_change_status_issue(repo, issueid, username=None): """ Change issue status ------------------- Change the status of an issue. :: POST /api/0/<repo>/issue/<issue id>/status :: POST /api/0/fork/<username>/<repo>/issue/<issue id>/status Input ^^^^^ +-------------+---------+--------------+------------------------------+ | Key | Type | Optionality | Description | +=============+=========+==============+==============================+ | ``status`` | string | Mandatory | The new status of the issue | +-------------+---------+--------------+------------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "message": "Successfully edited issue #1" } """ repo = pagure.lib.get_project(SESSION, repo, user=username) output = {} if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('issue_tracker', True): raise pagure.exceptions.APIError( 404, error_code=APIERROR.ETRACKERDISABLED) if api_authenticated(): if repo != flask.g.token.project: raise pagure.exceptions.APIError( 401, error_code=APIERROR.EINVALIDTOK) issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOISSUE) if issue.private and not is_repo_admin(repo) \ and (not api_authenticated() or not issue.user.user == flask.g.fas_user.username): raise pagure.exceptions.APIError( 403, error_code=APIERROR.EISSUENOTALLOWED) status = pagure.lib.get_issue_statuses(SESSION) form = pagure.forms.StatusForm(status=status, csrf_enabled=False) if form.validate_on_submit(): new_status = form.status.data try: # Update status message = pagure.lib.edit_issue( SESSION, issue=issue, status=new_status, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() if message: output['message'] = message else: output['message'] = 'No changes' except pagure.exceptions.PagureException as err: raise pagure.exceptions.APIError( 400, error_code=APIERROR.ENOCODE, error=str(err)) except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) else: raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ) jsonout = flask.jsonify(output) return jsonout
def api_view_issue(repo, issueid, username=None): """ Issue information ----------------- Retrieve information of a specific issue. :: GET /api/0/<repo>/issue/<issue id> :: GET /api/0/fork/<username>/<repo>/issue/<issue id> The identifier provided can be either the unique identifier or the regular identifier used in the UI (for example ``24`` in ``/forks/user/test/issue/24``) Sample response ^^^^^^^^^^^^^^^ :: { "assignee": null, "blocks": [], "comments": [], "content": "This issue needs attention", "date_created": "1431414800", "depends": [], "id": 1, "private": false, "status": "Open", "tags": [], "title": "test issue", "user": { "fullname": "PY C", "name": "pingou" } } """ comments = flask.request.args.get('comments', True) if str(comments).lower() in ['0', 'False']: comments = False repo = pagure.lib.get_project(SESSION, repo, user=username) if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('issue_tracker', True): raise pagure.exceptions.APIError( 404, error_code=APIERROR.ETRACKERDISABLED) issue_id = issue_uid = None try: issue_id = int(issueid) except: issue_uid = issueid issue = pagure.lib.search_issues( SESSION, repo, issueid=issue_id, issueuid=issue_uid) if issue is None or issue.project != repo: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOISSUE) if api_authenticated(): if repo != flask.g.token.project: raise pagure.exceptions.APIError( 401, error_code=APIERROR.EINVALIDTOK) if issue.private and not is_repo_admin(repo) \ and (not api_authenticated() or not issue.user.user == flask.g.fas_user.username): raise pagure.exceptions.APIError( 403, error_code=APIERROR.EISSUENOTALLOWED) jsonout = flask.jsonify( issue.to_json(public=True, with_comments=comments)) return jsonout
def api_assign_issue(repo, issueid, username=None): """ Assign an issue --------------- Assign an issue to someone. :: POST /api/0/<repo>/issue/<issue id>/assign :: POST /api/0/fork/<username>/<repo>/issue/<issue id>/assign Input ^^^^^ +--------------+----------+---------------+---------------------------+ | Key | Type | Optionality | Description | +==============+==========+===============+===========================+ | ``assignee`` | string | Mandatory | | The username of the user| | | | | to assign the issue to. | +--------------+----------+---------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "message": "Issue assigned" } """ repo = pagure.lib.get_project(SESSION, repo, user=username) output = {} if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('issue_tracker', True): raise pagure.exceptions.APIError(404, error_code=APIERROR.ETRACKERDISABLED) if api_authenticated(): if repo != flask.g.token.project: raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOISSUE) if issue.private and not is_repo_admin(repo) \ and (not api_authenticated() or not issue.user.user == flask.g.fas_user.username): raise pagure.exceptions.APIError(403, error_code=APIERROR.EISSUENOTALLOWED) form = pagure.forms.AssignIssueForm(csrf_enabled=False) if form.validate_on_submit(): assignee = form.assignee.data try: # New comment message = pagure.lib.add_issue_assignee( SESSION, issue=issue, assignee=assignee, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() output['message'] = message except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) else: raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ) jsonout = flask.jsonify(output) return jsonout
def api_update_custom_field( repo, issueid, field, username=None, namespace=None): """ Update custom field ------------------- Update or reset the content of a custom field associated to an issue. :: POST /api/0/<repo>/issue/<issue id>/custom/<field> POST /api/0/<namespace>/<repo>/issue/<issue id>/custom/<field> :: POST /api/0/fork/<username>/<repo>/issue/<issue id>/custom/<field> POST /api/0/fork/<username>/<namespace>/<repo>/issue/<issue id>/custom/<field> Input ^^^^^ +----------------- +---------+--------------+-------------------------+ | Key | Type | Optionality | Description | +==================+=========+==============+=========================+ | ``value`` | string | Optional | The new value of the | | | | | custom field of interest| +----------------- +---------+--------------+-------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "message": "Custom field adjusted" } """ repo = pagure.lib.get_project( SESSION, repo, user=username, namespace=namespace) output = {} if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('issue_tracker', True): raise pagure.exceptions.APIError( 404, error_code=APIERROR.ETRACKERDISABLED) if api_authenticated(): if repo != flask.g.token.project: raise pagure.exceptions.APIError( 401, error_code=APIERROR.EINVALIDTOK) issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOISSUE) if issue.private and not is_repo_admin(repo) \ and (not api_authenticated() or not issue.user.user == flask.g.fas_user.username): raise pagure.exceptions.APIError( 403, error_code=APIERROR.EISSUENOTALLOWED) fields = {k.name: k for k in repo.issue_keys} if field not in fields: raise pagure.exceptions.APIError( 400, error_code=APIERROR.EINVALIDISSUEFIELD) key = fields[field] value = flask.request.form.get('value') if value: if key.key_type == 'link': links = value.split(',') for link in links: link = link.replace(' ', '') if not urlpattern.match(link): raise pagure.exceptions.APIError( 400, error_code=APIERROR.EINVALIDISSUEFIELD_LINK) try: message = pagure.lib.set_custom_key_value( SESSION, issue, key, value) SESSION.commit() if message: output['message'] = message else: output['message'] = 'No changes' except pagure.exceptions.PagureException as err: raise pagure.exceptions.APIError( 400, error_code=APIERROR.ENOCODE, error=str(err)) except SQLAlchemyError as err: # pragma: no cover print err SESSION.rollback() raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) jsonout = flask.jsonify(output) return jsonout
def api_subscribe_issue(repo, issueid, username=None, namespace=None): """ Subscribe to an issue --------------------- Allows someone to subscribe or unscribe to the notifications related to an issue. :: POST /api/0/<repo>/issue/<issue id>/subscribe POST /api/0/<namespace>/<repo>/issue/<issue id>/subscribe :: POST /api/0/fork/<username>/<repo>/issue/<issue id>/subscribe POST /api/0/fork/<username>/<namespace>/<repo>/issue/<issue id>/subscribe Input ^^^^^ +--------------+----------+---------------+---------------------------+ | Key | Type | Optionality | Description | +==============+==========+===============+===========================+ | ``status`` | boolean | Mandatory | | The subscription status | | | | | to subscribe or | | | | | unsubscribe to the. | | | | | issue. | +--------------+----------+---------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "message": "User subscribed" } """ repo = pagure.lib.get_project( SESSION, repo, user=username, namespace=namespace) output = {} if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('issue_tracker', True): raise pagure.exceptions.APIError( 404, error_code=APIERROR.ETRACKERDISABLED) if api_authenticated(): if repo != flask.g.token.project: raise pagure.exceptions.APIError( 401, error_code=APIERROR.EINVALIDTOK) issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOISSUE) if issue.private and not is_repo_admin(repo) \ and (not api_authenticated() or not issue.user.user == flask.g.fas_user.username): raise pagure.exceptions.APIError( 403, error_code=APIERROR.EISSUENOTALLOWED) form = pagure.forms.SubscribtionForm(csrf_enabled=False) if form.validate_on_submit(): status = str(form.status.data).strip().lower() in ['1', 'true'] try: # Toggle subscribtion message = pagure.lib.set_watch_obj( SESSION, user=flask.g.fas_user.username, obj=issue, watch_status=status ) SESSION.commit() output['message'] = message except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) else: raise pagure.exceptions.APIError( 400, error_code=APIERROR.EINVALIDREQ, errors=form.errors) jsonout = flask.jsonify(output) return jsonout
def api_change_status_issue(repo, issueid, username=None): """ Change issue status ------------------- This endpoint can be used to change the status of an issue :: /api/0/<repo>/issue/<issue id>/status /api/0/fork/<username>/<repo>/issue/<issue id>/status Accepts POST queries only. :arg status: The new status of the specified issue Sample response: :: { "message": "Edited successfully issue #1" } """ repo = pagure.lib.get_project(SESSION, repo, user=username) output = {} if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('issue_tracker', True): raise pagure.exceptions.APIError( 404, error_code=APIERROR.ETRACKERDISABLED) if repo != flask.g.token.project: raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOISSUE) if issue.private and not is_repo_admin(repo) \ and (not api_authenticated() or not issue.user.user == flask.g.fas_user.username): raise pagure.exceptions.APIError( 403, error_code=APIERROR.EISSUENOTALLOWED) status = pagure.lib.get_issue_statuses(SESSION) form = pagure.forms.StatusForm(status=status, csrf_enabled=False) if form.validate_on_submit(): new_status = form.status.data try: # Update status message = pagure.lib.edit_issue( SESSION, issue=issue, status=new_status, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() if message: output['message'] = message else: output['message'] = 'No changes' except pagure.exceptions.PagureException, err: raise pagure.exceptions.APIError( 400, error_code=APIERROR.ENOCODE, error=str(err)) except SQLAlchemyError, err: # pragma: no cover SESSION.rollback() raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)
def api_view_issue(repo, issueid, username=None): """ Issue information ----------------- This endpoint can be used to retrieve information about a specific issue/ticket :: /api/0/<repo>/issue/<issue id> /api/0/fork/<username>/<repo>/issue/<issue id> Accepts GET queries only. Sample response: :: { "assignee": null, "blocks": [], "comments": [], "content": "This issue needs attention", "date_created": "1431414800", "depends": [], "id": 1, "private": false, "status": "Open", "tags": [], "title": "test issue", "user": { "fullname": "PY C", "name": "pingou" } } """ repo = pagure.lib.get_project(SESSION, repo, user=username) if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('issue_tracker', True): raise pagure.exceptions.APIError( 404, error_code=APIERROR.ETRACKERDISABLED) issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOISSUE) if api_authenticated(): if repo != flask.g.token.project: raise pagure.exceptions.APIError( 401, error_code=APIERROR.EINVALIDTOK) if issue.private and not is_repo_admin(repo) \ and (not api_authenticated() or not issue.user.user == flask.g.fas_user.username): raise pagure.exceptions.APIError( 403, error_code=APIERROR.EISSUENOTALLOWED) jsonout = flask.jsonify(issue.to_json(public=True)) return jsonout
def api_view_issues(repo, username=None): """ List project's issues --------------------- This endpoint can be used to retrieve the list of all issues of the specified project :: /api/0/<repo>/issues /api/0/fork/<username>/<repo>/issues Accepts GET queries only. :kwarg status: The status of the issues to return, default to 'Open' :kwarg tags: One or more tags to filter the issues returned. If you want to wish to filter for issues not having a specific tag you can mark the tag with an exclamation mark in front of it, for example to get all the issues not tagged as ``easyfix`` you can filter using the tag ``!easyfix`` :kwarg assignee: Filters the issues returned by the user they are assigned to :kwarg author: Filters the issues returned by the user that opened the issue Sample response: :: { "args": { "assignee": null, "author": null, "status": null, "tags": [] }, "issues": [ { "assignee": null, "blocks": [], "comments": [ { "comment": "bing", "date_created": "1427441560", "id": 379, "parent": null, "user": { "fullname": "PY.C", "name": "pingou" } } ], "content": "bar", "date_created": "1427441555", "depends": [], "id": 1, "private": false, "status": "Open", "tags": [], "title": "foo", "user": { "fullname": "PY.C", "name": "pingou" } }, { "assignee": null, "blocks": [], "comments": [], "content": "report", "date_created": "1427442076", "depends": [], "id": 2, "private": false, "status": "Open", "tags": [], "title": "bug", "user": { "fullname": "PY.C", "name": "pingou" } } ] } Second example: { "args": { "assignee": null, "author": null, "status": "Closed", "tags": [ "0.1" ] }, "issues": [ { "assignee": null, "blocks": [], "comments": [], "content": "asd", "date_created": "1427442217", "depends": [], "id": 4, "private": false, "status": "Fixed", "tags": [ "0.1" ], "title": "bug", "user": { "fullname": "PY.C", "name": "pingou" } } ] } """ repo = pagure.lib.get_project(SESSION, repo, user=username) if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('issue_tracker', True): raise pagure.exceptions.APIError( 404, error_code=APIERROR.ETRACKERDISABLED) status = flask.request.args.get('status', None) tags = flask.request.args.getlist('tags') tags = [tag.strip() for tag in tags if tag.strip()] assignee = flask.request.args.get('assignee', None) author = flask.request.args.get('author', None) # Hide private tickets private = False # If user is authenticated, show him/her his/her private tickets if api_authenticated(): if repo != flask.g.token.project: raise pagure.exceptions.APIError( 401, error_code=APIERROR.EINVALIDTOK) private = flask.g.fas_user.username # If user is repo admin, show all tickets included the private ones if is_repo_admin(repo): private = None if status is not None: if status.lower() == 'closed': issues = pagure.lib.search_issues( SESSION, repo, closed=True, tags=tags, assignee=assignee, author=author, private=private, ) else: issues = pagure.lib.search_issues( SESSION, repo, status=status, tags=tags, assignee=assignee, author=author, private=private, ) else: issues = pagure.lib.search_issues( SESSION, repo, status='Open', tags=tags, assignee=assignee, author=author, private=private) jsonout = flask.jsonify({ 'issues': [issue.to_json(public=True) for issue in issues], 'args': { 'status': status, 'tags': tags, 'assignee': assignee, 'author': author, } }) return jsonout
def api_comment_issue(repo, issueid, username=None): """ Comment to an issue ------------------- Add a comment to an issue. :: POST /api/0/<repo>/issue/<issue id>/comment :: POST /api/0/fork/<username>/<repo>/issue/<issue id>/comment Input ^^^^^ +--------------+----------+---------------+---------------------------+ | Key | Type | Optionality | Description | +==============+==========+===============+===========================+ | ``comment`` | string | Mandatory | | The comment to add to | | | | | the issue | +--------------+----------+---------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "message": "Comment added" } """ repo = pagure.lib.get_project(SESSION, repo, user=username) output = {} if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('issue_tracker', True): raise pagure.exceptions.APIError(404, error_code=APIERROR.ETRACKERDISABLED) if repo.fullname != flask.g.token.project.fullname: raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOISSUE) if issue.private and not is_repo_admin(repo) \ and (not api_authenticated() or not issue.user.user == flask.g.fas_user.username): raise pagure.exceptions.APIError(403, error_code=APIERROR.EISSUENOTALLOWED) form = pagure.forms.CommentForm(csrf_enabled=False) if form.validate_on_submit(): comment = form.comment.data try: # New comment message = pagure.lib.add_issue_comment( SESSION, issue=issue, comment=comment, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() output['message'] = message except SQLAlchemyError, err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)
def api_comment_issue(repo, issueid, username=None): """ Comment to an issue ------------------- Add a comment to an issue. :: POST /api/0/<repo>/issue/<issue id>/comment :: POST /api/0/fork/<username>/<repo>/issue/<issue id>/comment Input ^^^^^ +--------------+----------+---------------+---------------------------+ | Key | Type | Optionality | Description | +==============+==========+===============+===========================+ | ``comment`` | string | Mandatory | | The comment to add to | | | | | the issue | +--------------+----------+---------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "message": "Comment added" } """ repo = pagure.lib.get_project(SESSION, repo, user=username) output = {} if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('issue_tracker', True): raise pagure.exceptions.APIError( 404, error_code=APIERROR.ETRACKERDISABLED) if repo.fullname != flask.g.token.project.fullname: raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOISSUE) if issue.private and not is_repo_admin(repo) \ and (not api_authenticated() or not issue.user.user == flask.g.fas_user.username): raise pagure.exceptions.APIError( 403, error_code=APIERROR.EISSUENOTALLOWED) form = pagure.forms.CommentForm(csrf_enabled=False) if form.validate_on_submit(): comment = form.comment.data try: # New comment message = pagure.lib.add_issue_comment( SESSION, issue=issue, comment=comment, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() output['message'] = message except SQLAlchemyError, err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)
def api_view_issues(repo, username=None): """ List project's issues --------------------- List issues of a project. :: GET /api/0/<repo>/issues :: GET /api/0/fork/<username>/<repo>/issues Parameters ^^^^^^^^^^ +---------------+---------+--------------+---------------------------+ | Key | Type | Optionality | Description | +===============+=========+==============+===========================+ | ``status`` | string | Optional | | Filters the status of | | | | | issues. Fetches all the | | | | | issues if status is | | | | | ``all``. Default: | | | | | ``Open`` | +---------------+---------+--------------+---------------------------+ | ``tags`` | string | Optional | | A list of tags you | | | | | wish to filter. If | | | | | you want to filter | | | | | for issues not having | | | | | a tag, add an | | | | | exclamation mark in | | | | | front of it | +---------------+---------+--------------+---------------------------+ | ``assignee`` | string | Optional | | Filter the issues | | | | | by assignee | +---------------+---------+--------------+---------------------------+ | ``author`` | string | Optional | | Filter the issues | | | | | by creator | +---------------+---------+--------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "args": { "assignee": null, "author": null, "status": "Closed", "tags": [ "0.1" ] }, "total_issues": 1, "issues": [ { "assignee": null, "blocks": [], "comments": [], "content": "asd", "date_created": "1427442217", "depends": [], "id": 4, "private": false, "status": "Fixed", "tags": [ "0.1" ], "title": "bug", "user": { "fullname": "PY.C", "name": "pingou" } } ] } """ repo = pagure.lib.get_project(SESSION, repo, user=username) if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('issue_tracker', True): raise pagure.exceptions.APIError( 404, error_code=APIERROR.ETRACKERDISABLED) status = flask.request.args.get('status', None) tags = flask.request.args.getlist('tags') tags = [tag.strip() for tag in tags if tag.strip()] assignee = flask.request.args.get('assignee', None) author = flask.request.args.get('author', None) # Hide private tickets private = False # If user is authenticated, show him/her his/her private tickets if api_authenticated(): if repo != flask.g.token.project: raise pagure.exceptions.APIError( 401, error_code=APIERROR.EINVALIDTOK) private = flask.g.fas_user.username # If user is repo admin, show all tickets included the private ones if is_repo_admin(repo): private = None if status is not None: params = { 'session': SESSION, 'repo': repo, 'tags': tags, 'assignee': assignee, 'author': author, 'private': private } if status.lower() == 'closed': params.update({'closed': True}) elif status.lower() != 'all': params.update({'status': status}) issues = pagure.lib.search_issues(**params) else: issues = pagure.lib.search_issues( SESSION, repo, status='Open', tags=tags, assignee=assignee, author=author, private=private) jsonout = flask.jsonify({ 'total_issues': len(issues), 'issues': [issue.to_json(public=True) for issue in issues], 'args': { 'status': status, 'tags': tags, 'assignee': assignee, 'author': author, } }) return jsonout
def api_subscribe_pull_request(repo, requestid, username=None, namespace=None): """ Subscribe to an pull-request ---------------------------- Allows someone to subscribe to or unsubscribe from the notifications related to a pull-request. :: POST /api/0/<repo>/pull-request/<request id>/subscribe POST /api/0/<namespace>/<repo>/pull-request/<request id>/subscribe :: POST /api/0/fork/<username>/<repo>/pull-request/<request id>/subscribe POST /api/0/fork/<username>/<namespace>/<repo>/pull-request/<request id>/subscribe Input ^^^^^ +--------------+----------+---------------+---------------------------+ | Key | Type | Optionality | Description | +==============+==========+===============+===========================+ | ``status`` | boolean | Mandatory | The intended subscription | | | | | status. ``true`` for | | | | | subscribing, ``false`` | | | | | for unsubscribing. | +--------------+----------+---------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "message": "User subscribed" } """ # noqa repo = get_authorized_api_project(SESSION, repo, user=username, namespace=namespace) output = {} if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('pull_requests', True): raise pagure.exceptions.APIError( 404, error_code=APIERROR.EPULLREQUESTSDISABLED) if api_authenticated(): if flask.g.token.project and repo != flask.g.token.project: raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) request = pagure.lib.search_pull_requests(SESSION, project_id=repo.id, requestid=requestid) if not request: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOREQ) form = pagure.forms.SubscribtionForm(csrf_enabled=False) if form.validate_on_submit(): status = str(form.status.data).strip().lower() in ['1', 'true'] try: # Toggle subscribtion message = pagure.lib.set_watch_obj(SESSION, user=flask.g.fas_user.username, obj=request, watch_status=status) SESSION.commit() output['message'] = message except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) else: raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ, errors=form.errors) jsonout = flask.jsonify(output) return jsonout
def api_view_issue_comment(repo, issueid, commentid, username=None): """ Comment of an issue -------------------- Retrieve a specific comment of an issue. :: GET /api/0/<repo>/issue/<issue id>/comment/<comment id> :: GET /api/0/fork/<username>/<repo>/issue/<issue id>/comment/<comment id> The identifier provided can be either the unique identifier or the regular identifier used in the UI (for example ``24`` in ``/forks/user/test/issue/24``) Sample response ^^^^^^^^^^^^^^^ :: { "avatar_url": "https://seccdn.libravatar.org/avatar/...", "comment": "9", "comment_date": "2015-07-01 15:08", "date_created": "1435756127", "id": 464, "parent": null, "user": { "fullname": "P.-Y.C.", "name": "pingou" } } """ repo = pagure.lib.get_project(SESSION, repo, user=username) if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('issue_tracker', True): raise pagure.exceptions.APIError( 404, error_code=APIERROR.ETRACKERDISABLED) issue_id = issue_uid = None try: issue_id = int(issueid) except: issue_uid = issueid issue = pagure.lib.search_issues( SESSION, repo, issueid=issue_id, issueuid=issue_uid) if issue is None or issue.project != repo: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOISSUE) if api_authenticated(): if repo != flask.g.token.project: raise pagure.exceptions.APIError( 401, error_code=APIERROR.EINVALIDTOK) if issue.private and not is_repo_admin(issue.project) \ and (not api_authenticated() or not issue.user.user == flask.g.fas_user.username): raise pagure.exceptions.APIError( 403, error_code=APIERROR.EISSUENOTALLOWED) comment = pagure.lib.get_issue_comment(SESSION, issue.uid, commentid) if not comment: raise pagure.exceptions.APIError( 404, error_code=APIERROR.ENOCOMMENT) output = comment.to_json(public=True) output['avatar_url'] = pagure.lib.avatar_url_from_openid( comment.user.default_email, size=16) output['comment_date'] = comment.date_created.strftime( '%Y-%m-%d %H:%M:%S') jsonout = flask.jsonify(output) return jsonout
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 :: GET /api/0/group/some_group_name?projects=1&acl=commit Input ^^^^^ +------------------+---------+--------------+-----------------------------+ | Key | Type | Optionality | Description | +==================+=========+==============+=============================+ | ``group name`` | str | Mandatory | The name of the group to | | | | | retrieve information about. | +------------------+---------+--------------+-----------------------------+ | ``projects`` | bool | Optional | Specifies whether to include| | | | | projects in the data | | | | | returned. | +------------------+---------+--------------+-----------------------------+ | ``acl`` | str | Optional | Filter the project returned | | | | | (if any) to those where the | | | | | has the specified ACL level.| | | | | Can be any of: ``admin``, | | | | | ``commit`` or ``ticket``. | +------------------+---------+--------------+-----------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "creator": { "default_email": "*****@*****.**", "emails": [ "*****@*****.**" ], "fullname": "User1", "name": "user1" }, "date_created": "1492011511", "description": "Some Group", "display_name": "Some Group", "group_type": "user", "members": [ "user1", "user2" ], "name": "some_group_name" } :: { "creator": { "default_email": "*****@*****.**", "emails": [ "*****@*****.**" ], "fullname": "User1", "name": "user1" }, "date_created": "1492011511", "description": "Some Group", "display_name": "Some Group", "group_type": "user", "members": [ "user1", "user2" ], "name": "some_group_name", "projects": [], } """ # noqa projects = flask.request.values.get('projects', '').strip().lower() in ['1', 'true'] acl = flask.request.values.get('acl', '').strip().lower() or None if acl == 'ticket': acl = ['admin', 'commit', 'ticket'] elif acl == 'commit': acl = ['commit', 'admin'] elif acl: acl = [acl] group = pagure.lib.search_groups(SESSION, group_name=group) if not group: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOGROUP) output = group.to_json(public=(not pagure.api_authenticated())) if projects and not acl: output['projects'] = [ project.to_json(public=True) for project in group.projects ] elif projects and acl: output['projects'] = [ pg.project.to_json(public=True) for pg in group.projects_groups if pg.access in acl ] jsonout = flask.jsonify(output) jsonout.status_code = 200 return jsonout
def api_assign_issue(repo, issueid, username=None): """ Assign an issue --------------- Assign an issue to someone. :: POST /api/0/<repo>/issue/<issue id>/assign :: POST /api/0/fork/<username>/<repo>/issue/<issue id>/assign Input ^^^^^ +--------------+----------+---------------+---------------------------+ | Key | Type | Optionality | Description | +==============+==========+===============+===========================+ | ``assignee`` | string | Mandatory | | The username of the user| | | | | to assign the issue to. | +--------------+----------+---------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "message": "Issue assigned" } """ repo = pagure.lib.get_project(SESSION, repo, user=username) output = {} if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('issue_tracker', True): raise pagure.exceptions.APIError( 404, error_code=APIERROR.ETRACKERDISABLED) if api_authenticated(): if repo != flask.g.token.project: raise pagure.exceptions.APIError( 401, error_code=APIERROR.EINVALIDTOK) issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOISSUE) if issue.private and not is_repo_admin(repo) \ and (not api_authenticated() or not issue.user.user == flask.g.fas_user.username): raise pagure.exceptions.APIError( 403, error_code=APIERROR.EISSUENOTALLOWED) form = pagure.forms.AssignIssueForm(csrf_enabled=False) if form.validate_on_submit(): assignee = form.assignee.data try: # New comment message = pagure.lib.add_issue_assignee( SESSION, issue=issue, assignee=assignee, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() output['message'] = message except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) else: raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ) jsonout = flask.jsonify(output) return jsonout
def api_view_issues(repo, username=None, namespace=None): """ List project's issues --------------------- List issues of a project. :: GET /api/0/<repo>/issues GET /api/0/<namespace>/<repo>/issues :: GET /api/0/fork/<username>/<repo>/issues GET /api/0/fork/<username>/<namespace>/<repo>/issues Parameters ^^^^^^^^^^ +---------------+---------+--------------+---------------------------+ | Key | Type | Optionality | Description | +===============+=========+==============+===========================+ | ``status`` | string | Optional | | Filters the status of | | | | | issues. Fetches all the | | | | | issues if status is | | | | | ``all``. Default: | | | | | ``Open`` | +---------------+---------+--------------+---------------------------+ | ``tags`` | string | Optional | | A list of tags you | | | | | wish to filter. If | | | | | you want to filter | | | | | for issues not having | | | | | a tag, add an | | | | | exclamation mark in | | | | | front of it | +---------------+---------+--------------+---------------------------+ | ``assignee`` | string | Optional | | Filter the issues | | | | | by assignee | +---------------+---------+--------------+---------------------------+ | ``author`` | string | Optional | | Filter the issues | | | | | by creator | +---------------+---------+--------------+---------------------------+ | ``milestones``| list of | Optional | | Filter the issues | | | strings | | by milestone | +---------------+---------+--------------+---------------------------+ | ``priority`` | string | Optional | | Filter the issues | | | | | by priority | +---------------+---------+--------------+---------------------------+ | ``no_stones`` | boolean | Optional | | If true returns only the| | | | | issues having no | | | | | milestone, if false | | | | | returns only the issues | | | | | having a milestone | +---------------+---------+--------------+---------------------------+ | ``since`` | string | Optional | | Filter the issues | | | | | updated after this date.| | | | | The date can either be | | | | | provided as an unix date| | | | | or in the format Y-M-D | +---------------+---------+--------------+---------------------------+ | ``order`` | string | Optional | | Set the ordering of the | | | | | issues. This can be | | | | | ``asc`` or ``desc``. | | | | | Default: ``desc`` | +---------------+---------+--------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "args": { "assignee": null, "author": null, 'milestones': [], 'no_stones': null, 'order': null, 'priority': null, "since": null, "status": "Closed", "tags": [ "0.1" ] }, "total_issues": 1, "issues": [ { "assignee": null, "blocks": ["1"], "comments": [], "content": "asd", "date_created": "1427442217", "depends": [], "id": 4, "private": false, "status": "Fixed", "tags": [ "0.1" ], "title": "bug", "user": { "fullname": "PY.C", "name": "pingou" } } ] } """ repo = _get_repo(repo, username, namespace) _check_issue_tracker(repo) _check_token(repo) assignee = flask.request.args.get('assignee', None) author = flask.request.args.get('author', None) milestone = flask.request.args.getlist('milestones', None) no_stones = flask.request.args.get('no_stones', None) if no_stones is not None: if str(no_stones).lower() in ['1', 'true', 't']: no_stones = True else: no_stones = False priority = flask.request.args.get('priority', None) since = flask.request.args.get('since', None) order = flask.request.args.get('order', None) status = flask.request.args.get('status', None) tags = flask.request.args.getlist('tags') tags = [tag.strip() for tag in tags if tag.strip()] priority_key = None if priority: found = False if priority in repo.priorities: found = True priority_key = int(priority) else: for key, val in repo.priorities.items(): if val.lower() == priority.lower(): priority_key = key found = True break if not found: raise pagure.exceptions.APIError( 400, error_code=APIERROR.EINVALIDPRIORITY) # Hide private tickets private = False # If user is authenticated, show him/her his/her private tickets if api_authenticated(): private = flask.g.fas_user.username # If user is repo committer, show all tickets included the private ones if is_repo_committer(repo): private = None params = { 'session': SESSION, 'repo': repo, 'tags': tags, 'assignee': assignee, 'author': author, 'private': private, 'milestones': milestone, 'priority': priority_key, 'order': order, 'no_milestones': no_stones, } if status is not None: if status.lower() == 'all': params.update({'status': None}) elif status.lower() == 'closed': params.update({'closed': True}) else: params.update({'status': status}) else: params.update({'status': 'Open'}) updated_after = None if since: # Validate and convert the time if since.isdigit(): # We assume its a timestamp, so convert it to datetime try: updated_after = datetime.datetime.fromtimestamp(int(since)) except ValueError: raise pagure.exceptions.APIError( 400, error_code=APIERROR.ETIMESTAMP) else: # We assume datetime format, so validate it try: updated_after = datetime.datetime.strptime(since, '%Y-%m-%d') except ValueError: raise pagure.exceptions.APIError(400, error_code=APIERROR.EDATETIME) params.update({'updated_after': updated_after}) issues = pagure.lib.search_issues(**params) jsonout = flask.jsonify({ 'total_issues': len(issues), 'issues': [issue.to_json(public=True) for issue in issues], 'args': { 'assignee': assignee, 'author': author, 'milestones': milestone, 'no_stones': no_stones, 'order': order, 'priority': priority, 'since': since, 'status': status, 'tags': tags, } }) return jsonout