Merge branch 'master' into stable

remotes/origin/live-notifications
Jesús Espino 2017-03-23 18:57:43 +01:00
commit e9dbe4519f
7 changed files with 108 additions and 32 deletions

View File

@ -55,4 +55,6 @@ urls = {
"project-transfer": "/project/{0}/transfer/{1}", # project.slug, project.transfer_token "project-transfer": "/project/{0}/transfer/{1}", # project.slug, project.transfer_token
"project-admin": "/login?next=/project/{0}/admin/project-profile/details", # project.slug "project-admin": "/login?next=/project/{0}/admin/project-profile/details", # project.slug
"project-import-jira": "/project/new/import/jira?url={}",
} }

View File

@ -22,5 +22,8 @@ class InvalidRequest(Exception):
class InvalidAuthResult(Exception): class InvalidAuthResult(Exception):
pass pass
class InvalidServiceConfiguration(Exception):
pass
class FailedRequest(Exception): class FailedRequest(Exception):
pass pass

View File

@ -13,6 +13,8 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import uuid
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.conf import settings 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.users.gravatar import get_user_gravatar_id
from taiga.importers import permissions from taiga.importers import permissions
from taiga.importers import exceptions
from taiga.importers.services import resolve_users_bindings from taiga.importers.services import resolve_users_bindings
from .normal import JiraNormalImporter from .normal import JiraNormalImporter
from .agile import JiraAgileImporter from .agile import JiraAgileImporter
@ -178,18 +181,21 @@ class JiraImporterViewSet(viewsets.ViewSet):
if not jira_url: if not jira_url:
raise exc.WrongArguments(_("The url param is needed")) raise exc.WrongArguments(_("The url param is needed"))
(oauth_token, oauth_secret, url) = JiraNormalImporter.get_auth_url( try:
jira_url, (oauth_token, oauth_secret, url) = JiraNormalImporter.get_auth_url(
settings.IMPORTERS.get('jira', {}).get('consumer_key', None), jira_url,
settings.IMPORTERS.get('jira', {}).get('cert', None), settings.IMPORTERS.get('jira', {}).get('consumer_key', None),
True 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( (auth_data, created) = AuthData.objects.get_or_create(
user=request.user, user=request.user,
key="jira-oauth", key="jira-oauth",
defaults={ defaults={
"value": "", "value": uuid.uuid4().hex,
"extra": {}, "extra": {},
} }
) )
@ -208,6 +214,7 @@ class JiraImporterViewSet(viewsets.ViewSet):
try: try:
oauth_data = request.user.auth_data.get(key="jira-oauth") 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_token = oauth_data.extra['oauth_token']
oauth_secret = oauth_data.extra['oauth_secret'] oauth_secret = oauth_data.extra['oauth_secret']
server_url = oauth_data.extra['url'] server_url = oauth_data.extra['url']
@ -219,7 +226,8 @@ class JiraImporterViewSet(viewsets.ViewSet):
settings.IMPORTERS.get('jira', {}).get('cert', None), settings.IMPORTERS.get('jira', {}).get('cert', None),
oauth_token, oauth_token,
oauth_secret, oauth_secret,
True oauth_verifier,
False
) )
except Exception as e: except Exception as e:
raise exc.WrongArguments(_("Invalid or expired auth token")) raise exc.WrongArguments(_("Invalid or expired auth token"))

View File

@ -17,12 +17,13 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import requests import requests
from urllib.parse import parse_qsl from urllib.parse import parse_qsl, quote_plus
from oauthlib.oauth1 import SIGNATURE_RSA from oauthlib.oauth1 import SIGNATURE_RSA
from requests_oauthlib import OAuth1 from requests_oauthlib import OAuth1
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.conf import settings
from taiga.users.models import User from taiga.users.models import User
from taiga.projects.models import Points 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.models import HistoryEntry
from taiga.projects.history.choices import HistoryType from taiga.projects.history.choices import HistoryType
from taiga.mdrender.service import render as mdrender 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 = { EPIC_COLORS = {
"ghx-label-0": "#ffffff", "ghx-label-0": "#ffffff",
@ -734,9 +737,13 @@ class JiraImporterCommon:
if verify is None: if verify is None:
verify = server.startswith('https') 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( r = requests.post(
server + '/plugins/servlet/oauth/request-token', verify=verify, auth=oauth) 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 = dict(parse_qsl(r.text))
request_token = request['oauth_token'] request_token = request['oauth_token']
request_token_secret = request['oauth_token_secret'] request_token_secret = request['oauth_token_secret']
@ -748,13 +755,16 @@ class JiraImporterCommon:
) )
@classmethod @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( oauth = OAuth1(
consumer_key, consumer_key,
signature_method=SIGNATURE_RSA, signature_method=SIGNATURE_RSA,
callback_uri=callback_uri,
rsa_key=key_cert_data, rsa_key=key_cert_data,
resource_owner_key=request_token, 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) r = requests.post(server + '/plugins/servlet/oauth/access-token', verify=verify, auth=oauth)
access = dict(parse_qsl(r.text)) access = dict(parse_qsl(r.text))

View File

@ -66,24 +66,34 @@ class ResolverViewSet(viewsets.ViewSet):
if data["ref"]: if data["ref"]:
ref_found = False # No need to continue once one ref is found 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): try:
epic = project.epics.filter(ref=data["ref"]).first() value = int(data["ref"])
if epic:
result["epic"] = epic.pk if user_has_perm(request.user, "view_epics", project):
ref_found = True epic = project.epics.filter(ref=value).first()
if user_has_perm(request.user, "view_us", project): if epic:
us = project.user_stories.filter(ref=data["ref"]).first() result["epic"] = epic.pk
if us: ref_found = True
result["us"] = us.pk if ref_found is False and user_has_perm(request.user, "view_us", project):
ref_found = True us = project.user_stories.filter(ref=value).first()
if ref_found is False and user_has_perm(request.user, "view_tasks", project): if us:
task = project.tasks.filter(ref=data["ref"]).first() result["us"] = us.pk
if task: ref_found = True
result["task"] = task.pk if ref_found is False and user_has_perm(request.user, "view_tasks", project):
ref_found = True task = project.tasks.filter(ref=value).first()
if ref_found is False and user_has_perm(request.user, "view_issues", project): if task:
issue = project.issues.filter(ref=data["ref"]).first() result["task"] = task.pk
if issue: ref_found = True
result["issue"] = issue.pk 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) return response.Ok(result)

View File

@ -28,8 +28,8 @@ class ResolverValidator(validators.Validator):
us = serializers.IntegerField(required=False) us = serializers.IntegerField(required=False)
task = serializers.IntegerField(required=False) task = serializers.IntegerField(required=False)
issue = serializers.IntegerField(required=False) issue = serializers.IntegerField(required=False)
ref = serializers.IntegerField(required=False)
wikipage = serializers.CharField(max_length=512, required=False) wikipage = serializers.CharField(max_length=512, required=False)
ref = serializers.CharField(max_length=512, required=False)
def validate(self, attrs): def validate(self, attrs):
if "ref" in 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") raise ValidationError("'task' param is incompatible with 'ref' in the same request")
if "issue" in attrs: if "issue" in attrs:
raise ValidationError("'issue' param is incompatible with 'ref' in the same request") 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 return attrs

View File

@ -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, response = client.json.get("{}?project={}&ref={}&milestone={}".format(url, project.slug, us.ref,
milestone.slug)) milestone.slug))
assert response.status_code == 200 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