diff --git a/taiga/front/urls.py b/taiga/front/urls.py index 393913e3..244e0a72 100644 --- a/taiga/front/urls.py +++ b/taiga/front/urls.py @@ -55,4 +55,6 @@ urls = { "project-transfer": "/project/{0}/transfer/{1}", # project.slug, project.transfer_token "project-admin": "/login?next=/project/{0}/admin/project-profile/details", # project.slug + + "project-import-jira": "/project/new/import/jira?url={}", } diff --git a/taiga/importers/exceptions.py b/taiga/importers/exceptions.py index 0646095d..df78efd1 100644 --- a/taiga/importers/exceptions.py +++ b/taiga/importers/exceptions.py @@ -22,5 +22,8 @@ class InvalidRequest(Exception): class InvalidAuthResult(Exception): pass +class InvalidServiceConfiguration(Exception): + pass + class FailedRequest(Exception): pass diff --git a/taiga/importers/jira/api.py b/taiga/importers/jira/api.py index 9c5d0b4d..5606a732 100644 --- a/taiga/importers/jira/api.py +++ b/taiga/importers/jira/api.py @@ -13,6 +13,8 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import uuid + from django.utils.translation import ugettext as _ from django.conf import settings @@ -25,6 +27,7 @@ from taiga.users.services import get_user_photo_url from taiga.users.gravatar import get_user_gravatar_id from taiga.importers import permissions +from taiga.importers import exceptions from taiga.importers.services import resolve_users_bindings from .normal import JiraNormalImporter from .agile import JiraAgileImporter @@ -178,18 +181,21 @@ class JiraImporterViewSet(viewsets.ViewSet): if not jira_url: raise exc.WrongArguments(_("The url param is needed")) - (oauth_token, oauth_secret, url) = JiraNormalImporter.get_auth_url( - jira_url, - settings.IMPORTERS.get('jira', {}).get('consumer_key', None), - settings.IMPORTERS.get('jira', {}).get('cert', None), - True - ) + try: + (oauth_token, oauth_secret, url) = JiraNormalImporter.get_auth_url( + jira_url, + settings.IMPORTERS.get('jira', {}).get('consumer_key', None), + settings.IMPORTERS.get('jira', {}).get('cert', None), + True + ) + except exceptions.InvalidServiceConfiguration: + raise exc.BadRequest(_("Invalid Jira server configuration.")) (auth_data, created) = AuthData.objects.get_or_create( user=request.user, key="jira-oauth", defaults={ - "value": "", + "value": uuid.uuid4().hex, "extra": {}, } ) @@ -208,6 +214,7 @@ class JiraImporterViewSet(viewsets.ViewSet): try: oauth_data = request.user.auth_data.get(key="jira-oauth") + oauth_verifier = request.DATA.get("oauth_verifier", None) oauth_token = oauth_data.extra['oauth_token'] oauth_secret = oauth_data.extra['oauth_secret'] server_url = oauth_data.extra['url'] @@ -219,7 +226,8 @@ class JiraImporterViewSet(viewsets.ViewSet): settings.IMPORTERS.get('jira', {}).get('cert', None), oauth_token, oauth_secret, - True + oauth_verifier, + False ) except Exception as e: raise exc.WrongArguments(_("Invalid or expired auth token")) diff --git a/taiga/importers/jira/common.py b/taiga/importers/jira/common.py index f1ffa691..c4c6c3eb 100644 --- a/taiga/importers/jira/common.py +++ b/taiga/importers/jira/common.py @@ -17,12 +17,13 @@ # along with this program. If not, see . import requests -from urllib.parse import parse_qsl +from urllib.parse import parse_qsl, quote_plus from oauthlib.oauth1 import SIGNATURE_RSA from requests_oauthlib import OAuth1 from django.core.files.base import ContentFile from django.contrib.contenttypes.models import ContentType +from django.conf import settings from taiga.users.models import User from taiga.projects.models import Points @@ -45,6 +46,8 @@ from taiga.projects.custom_attributes.models import (UserStoryCustomAttribute, from taiga.projects.history.models import HistoryEntry from taiga.projects.history.choices import HistoryType from taiga.mdrender.service import render as mdrender +from taiga.importers import exceptions +from taiga.front.templatetags.functions import resolve as resolve_front_url EPIC_COLORS = { "ghx-label-0": "#ffffff", @@ -734,9 +737,13 @@ class JiraImporterCommon: if verify is None: verify = server.startswith('https') - oauth = OAuth1(consumer_key, signature_method=SIGNATURE_RSA, rsa_key=key_cert_data) + callback_uri = resolve_front_url("project-import-jira", quote_plus(server)) + oauth = OAuth1(consumer_key, signature_method=SIGNATURE_RSA, rsa_key=key_cert_data, callback_uri=callback_uri) + r = requests.post( server + '/plugins/servlet/oauth/request-token', verify=verify, auth=oauth) + if r.status_code != 200: + raise exceptions.InvalidServiceConfiguration() request = dict(parse_qsl(r.text)) request_token = request['oauth_token'] request_token_secret = request['oauth_token_secret'] @@ -748,13 +755,16 @@ class JiraImporterCommon: ) @classmethod - def get_access_token(cls, server, consumer_key, key_cert_data, request_token, request_token_secret, verify=False): + def get_access_token(cls, server, consumer_key, key_cert_data, request_token, request_token_secret, request_verifier, verify=False): + callback_uri = resolve_front_url("project-import-jira", quote_plus(server)) oauth = OAuth1( consumer_key, signature_method=SIGNATURE_RSA, + callback_uri=callback_uri, rsa_key=key_cert_data, resource_owner_key=request_token, - resource_owner_secret=request_token_secret + resource_owner_secret=request_token_secret, + verifier=request_verifier, ) r = requests.post(server + '/plugins/servlet/oauth/access-token', verify=verify, auth=oauth) access = dict(parse_qsl(r.text)) diff --git a/taiga/projects/references/api.py b/taiga/projects/references/api.py index 10a08538..67cf9f1d 100644 --- a/taiga/projects/references/api.py +++ b/taiga/projects/references/api.py @@ -66,24 +66,34 @@ class ResolverViewSet(viewsets.ViewSet): if data["ref"]: ref_found = False # No need to continue once one ref is found - if ref_found is False and user_has_perm(request.user, "view_epics", project): - epic = project.epics.filter(ref=data["ref"]).first() - if epic: - result["epic"] = epic.pk - ref_found = True - if user_has_perm(request.user, "view_us", project): - us = project.user_stories.filter(ref=data["ref"]).first() - if us: - result["us"] = us.pk - ref_found = True - if ref_found is False and user_has_perm(request.user, "view_tasks", project): - task = project.tasks.filter(ref=data["ref"]).first() - if task: - result["task"] = task.pk - ref_found = True - if ref_found is False and user_has_perm(request.user, "view_issues", project): - issue = project.issues.filter(ref=data["ref"]).first() - if issue: - result["issue"] = issue.pk + try: + value = int(data["ref"]) + + if user_has_perm(request.user, "view_epics", project): + epic = project.epics.filter(ref=value).first() + if epic: + result["epic"] = epic.pk + ref_found = True + if ref_found is False and user_has_perm(request.user, "view_us", project): + us = project.user_stories.filter(ref=value).first() + if us: + result["us"] = us.pk + ref_found = True + if ref_found is False and user_has_perm(request.user, "view_tasks", project): + task = project.tasks.filter(ref=value).first() + if task: + result["task"] = task.pk + ref_found = True + if ref_found is False and user_has_perm(request.user, "view_issues", project): + issue = project.issues.filter(ref=value).first() + if issue: + result["issue"] = issue.pk + except: + value = data["ref"] + + if user_has_perm(request.user, "view_wiki_pages", project): + wiki_page = project.wiki_pages.filter(slug=value).first() + if wiki_page: + result["wikipage"] = wiki_page.pk return response.Ok(result) diff --git a/taiga/projects/references/validators.py b/taiga/projects/references/validators.py index ad51e42c..3b2baba1 100644 --- a/taiga/projects/references/validators.py +++ b/taiga/projects/references/validators.py @@ -28,8 +28,8 @@ class ResolverValidator(validators.Validator): us = serializers.IntegerField(required=False) task = serializers.IntegerField(required=False) issue = serializers.IntegerField(required=False) - ref = serializers.IntegerField(required=False) wikipage = serializers.CharField(max_length=512, required=False) + ref = serializers.CharField(max_length=512, required=False) def validate(self, attrs): if "ref" in attrs: @@ -41,5 +41,7 @@ class ResolverValidator(validators.Validator): raise ValidationError("'task' param is incompatible with 'ref' in the same request") if "issue" in attrs: raise ValidationError("'issue' param is incompatible with 'ref' in the same request") + if "wikipage" in attrs: + raise ValidationError("'wikipage' param is incompatible with 'ref' in the same request") return attrs diff --git a/tests/integration/test_references_sequences.py b/tests/integration/test_references_sequences.py index 7bb035de..24cf639b 100644 --- a/tests/integration/test_references_sequences.py +++ b/tests/integration/test_references_sequences.py @@ -191,3 +191,44 @@ def test_params_validation_in_api_request(client, refmodels): response = client.json.get("{}?project={}&ref={}&milestone={}".format(url, project.slug, us.ref, milestone.slug)) assert response.status_code == 200 + + +@pytest.mark.django_db +def test_by_ref_calls_in_api_request(client, refmodels): + refmodels.Reference.objects.all().delete() + + user = factories.UserFactory.create() + project = factories.ProjectFactory.create(owner=user) + seqname1 = refmodels.make_sequence_name(project) + role = factories.RoleFactory.create(project=project) + factories.MembershipFactory.create(project=project, user=user, role=role, is_admin=True) + + epic = factories.EpicFactory.create(project=project) + milestone = factories.MilestoneFactory.create(project=project) + us = factories.UserStoryFactory.create(project=project) + task = factories.TaskFactory.create(project=project) + issue = factories.IssueFactory.create(project=project) + wiki_page = factories.WikiPageFactory.create(project=project) + + client.login(user) + + url = reverse("resolver-list") + response = client.json.get("{}?project={}&ref={}".format(url, project.slug, epic.ref)) + assert response.status_code == 200 + assert response.data["epic"] == epic.id + + response = client.json.get("{}?project={}&ref={}".format(url, project.slug, us.ref)) + assert response.status_code == 200 + assert response.data["us"] == us.id + + response = client.json.get("{}?project={}&ref={}".format(url, project.slug, task.ref)) + assert response.status_code == 200 + assert response.data["task"] == task.id + + response = client.json.get("{}?project={}&ref={}".format(url, project.slug, issue.ref)) + assert response.status_code == 200 + assert response.data["issue"] == issue.id + + response = client.json.get("{}?project={}&ref={}".format(url, project.slug, wiki_page.slug)) + assert response.status_code == 200 + assert response.data["wikipage"] == wiki_page.id