diff --git a/ursh/MANIFEST.in b/ursh/MANIFEST.in new file mode 100644 index 0000000..db61780 --- /dev/null +++ b/ursh/MANIFEST.in @@ -0,0 +1,4 @@ +graft indico_ursh/static +graft indico_ursh/translations + +global-exclude *.pyc __pycache__ .keep diff --git a/ursh/indico_ursh/__init__.py b/ursh/indico_ursh/__init__.py new file mode 100644 index 0000000..7e6aeec --- /dev/null +++ b/ursh/indico_ursh/__init__.py @@ -0,0 +1,22 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +from indico.util.i18n import make_bound_gettext + + +_ = make_bound_gettext('ursh') diff --git a/ursh/indico_ursh/blueprint.py b/ursh/indico_ursh/blueprint.py new file mode 100644 index 0000000..67d40ae --- /dev/null +++ b/ursh/indico_ursh/blueprint.py @@ -0,0 +1,25 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +from indico.core.plugins import IndicoPluginBlueprint + +from indico_ursh.controllers import RHShortenURL + + +blueprint = IndicoPluginBlueprint('ursh', 'indico_ursh') +blueprint.add_url_rule('/ursh', 'get_short_url', RHShortenURL, methods=('POST',)) diff --git a/ursh/indico_ursh/client/index.js b/ursh/indico_ursh/client/index.js new file mode 100644 index 0000000..9771580 --- /dev/null +++ b/ursh/indico_ursh/client/index.js @@ -0,0 +1,60 @@ +/* This file is part of Indico. + * Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN). + * + * Indico is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Indico is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Indico; if not, see . + */ + +import {handleAxiosError, indicoAxios} from 'indico/utils/axios'; + + +async function _makeUrshRequest(originalURL, triggerElement) { + const urshEndpoint = '/ursh'; + + let response; + try { + response = await indicoAxios.post(urshEndpoint, { + original_url: originalURL, + }); + } catch (error) { + handleAxiosError(error); + return; + } + + const data = response.data; + if (data.success) { + $(triggerElement).copyURLTooltip(data.msg).show(); + } else { + $(triggerElement).qtip({ + content: { + text: $T.gettext(`URL shortening service is unavailable: ${data.msg}`), + }, + hide: { + event: 'mouseleave', + fixed: true, + delay: 700, + }, + show: { + event: false, + ready: true, + } + }).show(); + } +} + + +$(document).on('click', '.ursh-get', (event) => { + event.preventDefault(); + const originalURL = $(event.target).attr('data-original-url'); + _makeUrshRequest(originalURL, event.target); +}); diff --git a/ursh/indico_ursh/controllers.py b/ursh/indico_ursh/controllers.py new file mode 100644 index 0000000..3998784 --- /dev/null +++ b/ursh/indico_ursh/controllers.py @@ -0,0 +1,51 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +import posixpath + +from flask import jsonify, request +from werkzeug.exceptions import BadRequest +from werkzeug.urls import url_parse + +from indico.core.config import config +from indico.web.rh import RH + +from indico_ursh.util import request_short_url + + +class RHShortenURL(RH): + """Make a request to the URL shortening service""" + + @staticmethod + def _resolve_full_url(original_url): + if url_parse(original_url).host is not None: + return original_url + original_url = original_url.lstrip('/') + return posixpath.join(config.BASE_URL, original_url) + + @staticmethod + def _check_host(full_url): + if url_parse(full_url).host != url_parse(config.BASE_URL).host: + raise BadRequest('Invalid host for URL shortening service') + + def _process(self): + original_url = request.json.get('original_url') + full_url = self._resolve_full_url(original_url) + self._check_host(full_url) + short_url = request_short_url(full_url) + return jsonify(success=True, msg=short_url) diff --git a/ursh/indico_ursh/plugin.py b/ursh/indico_ursh/plugin.py new file mode 100644 index 0000000..f445921 --- /dev/null +++ b/ursh/indico_ursh/plugin.py @@ -0,0 +1,63 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +from flask_pluginengine import render_plugin_template + +from indico.core.plugins import IndicoPlugin +from indico.web.forms.base import IndicoForm +from indico.web.views import WPBase + +from indico_ursh import _ +from indico_ursh.blueprint import blueprint +from wtforms.fields.core import StringField +from wtforms.fields.html5 import URLField +from wtforms.validators import DataRequired + + +class SettingsForm(IndicoForm): + api_key = StringField(_('API key'), [DataRequired()], + description=_('The API key to access the ursh service')) + api_host = URLField(_('API host'), [DataRequired()], + description=_('The ursh API host, providing the interface to generate short URLs')) + + +class UrshPlugin(IndicoPlugin): + """URL Shortener + + Provides a URL shortening service for indico assets, events, etc. + """ + + configurable = True + settings_form = SettingsForm + default_settings = { + 'api_key': '', + 'api_host': '', + } + + def init(self): + super(UrshPlugin, self).init() + self.template_hook('url-shortener-link', self._inject_ursh_button) + self.inject_bundle('main.js', WPBase) + + def get_blueprints(self): + return blueprint + + def _inject_ursh_button(self, target, element_type='a', element_class='', text='(short-url)', **kwargs): + if self.settings.get('api_key') and self.settings.get('api_host'): + return render_plugin_template('ursh_button.html', target=target, + element_type=element_type, element_class=element_class, text=text) diff --git a/ursh/indico_ursh/templates/ursh_button.html b/ursh/indico_ursh/templates/ursh_button.html new file mode 100644 index 0000000..f08c6c5 --- /dev/null +++ b/ursh/indico_ursh/templates/ursh_button.html @@ -0,0 +1,5 @@ +<{{ element_type }} class="ursh-get {{ element_class }}" + title="{% trans %}Obtain short URL{% endtrans %}" + data-original-url="{{ target }}"> + {{- text -}} + diff --git a/ursh/indico_ursh/util.py b/ursh/indico_ursh/util.py new file mode 100644 index 0000000..b9e3a10 --- /dev/null +++ b/ursh/indico_ursh/util.py @@ -0,0 +1,36 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +import requests +from werkzeug.exceptions import ServiceUnavailable + + +def request_short_url(original_url): + from indico_ursh.plugin import UrshPlugin + api_key = UrshPlugin.settings.get('api_key') + api_host = UrshPlugin.settings.get('api_host') + + if not api_key or not api_host: + raise ServiceUnavailable('Not configured') + + headers = {'Authorization': 'Bearer {api_key}'.format(api_key=api_key)} + response = requests.post(api_host, data=dict(url=original_url, allow_reuse=True), headers=headers) + response.raise_for_status() + + data = response.json() + return data['short_url'] diff --git a/ursh/setup.py b/ursh/setup.py new file mode 100644 index 0000000..7cde8f5 --- /dev/null +++ b/ursh/setup.py @@ -0,0 +1,43 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +from setuptools import find_packages, setup + + +setup( + name='indico-plugin-ursh', + version='3.0-dev', + description='URL shortening service for Indico', + url='https://github.com/indico/indico-plugins', + license='https://www.gnu.org/licenses/gpl-3.0.txt', + author='Indico Team', + author_email='indico-team@cern.ch', + packages=find_packages(), + zip_safe=False, + include_package_data=True, + install_requires=[ + 'indico>=2.2.dev0', + ], + classifiers=[ + 'Environment :: Plugins', + 'Environment :: Web Environment', + 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', + 'Programming Language :: Python :: 2.7' + ], + entry_points={'indico.plugins': {'ursh = indico_ursh.plugin:UrshPlugin'}} +) diff --git a/ursh/webpack-bundles.json b/ursh/webpack-bundles.json new file mode 100644 index 0000000..5f28835 --- /dev/null +++ b/ursh/webpack-bundles.json @@ -0,0 +1,5 @@ +{ + "entry": { + "main": "./index.js" + } +}