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))