-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
A basic working version of the bouncer. How it works: - This is a very simple Pyramid app with just one (real) view and a little JavaScript, no authorization or authentication, no sessions, no database, etc. - The Pyramid app starts up and reads it configuration (e.g. the Via URL, the Elasticsearch server's URL) from environment variables (no paster, no `.ini` files). - The user requests a page like `https://hpt.is/<id>/[path]`. - The server-side code reads the annotation ID from the URL, fetches the annotation from Elasticsearch directly, and extracts the document URI from the annotation - No consideration is given to whether the user is logged-in or whether annotations are private or belong to private groups. It may redirect a user to an annotation that they aren't authorized to see or that they need to login in order to see, at which point the client (Via or the Chrome extension) is responsible for dealing with that situation. The bouncer knows nothing about authentication or authorization. - If the annotation ID isn't found in Elasticsearch then the server responds with a simple 404 page. - Otherwise the server delivers a simple HTML page with a CSS animation that says "Redirecting". - The server renders a JSON data object into the page that includes both the Chrome extension URL and the Via URL for the annotation. - The page also includes a JavaScript file that runs on page load and redirects the browser by assigning to `window.location`. This JavaScript: - Reads the Chrome extension URL and Via URL from the JSON data object that the server rendered into the page. - Checks whether the global `chrome` object exists. If not then it redirects to the Via URL. - If the `chrome` object does exist then it checks whether our Chrome extension is installed by calling `chrome.runtime.sendMessage()`. - If the extension responds the JavaScript redirects the browser to the annotation's extension URL, if not it redirects to the Via URL. Known issues: - Sometimes the alt text "Redirecting" is shown in place of the Hypothesis logo image while the browser waits for the page being redirected to to load. This seems to happen in Firefox more than Chrome. What I think is happening (initial guess) is: * The JavaScript downloads and does the redirect before the image file has finished download. * At this point the browser no longer cares about the page, will not finish downloading and rendering the image, it's on to the next page. * However if the next page takes a while to load, the partially-rendered redirecting page may still be dsiplayed for a noticeable amount of time, and it shows the alt text instead of the image. Notes: - It supports Python version 3.5 only. - The unit tests, JavaScript modularization, continuous integration and linting tools, etc may seem excessive for such simple functionality. But I think it's good to start off as we mean to continue and lay a good foundation for when the requirements of this app become more complex. - The HTML page is a little heavy, it includes one HTML file, one CSS file, one SVG file, and one JavaScript file. This is all necessary to implement the redirecting page design as given. The CSS could be inlined in the HTML. The JavaScript could also be inlined but I prefer to keep it separate for unit testing, and also for modularity. The code as it is is very simple and doesn't require modularisation, but I think it's good to set a foundation for how we want to continue developing this if the logic required of the JavaScript becomes more complex. The design also calls for the Helvetica Neue or Helvetica fonts, I've left these font families in the CSS (they'll work if the user happens to have the fonts installed) but in the interests of keeping the page weight down I haven't included the font in the page. If the user doesn't have Helvetica it'll fall back to Arial or Lucida Grande. - There is no support for browsers without JavaScript. Without JavaScript we'd have no way of detecting that the Chrome extension is installed (nor would the Chrome extension work without JavaScript) so all we could do would be to redirect to Via, but Via is also useless without JavaScript. Maybe it should redirect to the annotation's standalone page at https://hypothes.is/, however _those_ pages currently require JavaScript as well (but they could easily be reimplemented not to). For now I haven't supported no-JS. - There's no reporting of stats/logs/crashes to anything yet. - The configs for Prospector, jshint, jscs etc are duplicated from <https://github.com/hypothesis/h>. In the future we may want to move these into a separate "Hypothesis Coding Standards" repo and figure out how to share them between repos. For now I've just duplicated them. (Also `CONTRIBUTING.rst`.)
- Loading branch information
Showing
27 changed files
with
915 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
*.egg-info | ||
*.pyc | ||
node_modules | ||
.coverage | ||
bouncer/static/scripts/bundle.js | ||
.cache |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
javascript: | ||
config_file: .jshintrc | ||
ignore_file: .jshintignore | ||
jscs: | ||
enabled: true | ||
config_file: .jscsrc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
{ | ||
"preset": "google", | ||
"requireSemicolons": true, | ||
|
||
"excludeFiles": [ | ||
"node_modules/**", | ||
], | ||
"maxErrors": 10, | ||
"disallowSpacesInAnonymousFunctionExpression": null, | ||
"disallowSpacesInFunctionDeclaration": { | ||
"beforeOpeningRoundBrace": true | ||
}, | ||
"disallowSpacesInFunctionExpression": null, | ||
"disallowSpacesInNamedFunctionExpression": { | ||
"beforeOpeningRoundBrace": true | ||
}, | ||
"requireSpacesInFunctionDeclaration": null, | ||
"requireSpacesInFunctionExpression": null, | ||
"requireSpacesInAnonymousFunctionExpression": { | ||
"beforeOpeningRoundBrace": true, | ||
"beforeOpeningCurlyBrace": true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
node_modules/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
{ | ||
"bitwise": true, | ||
"curly": true, | ||
"eqeqeq": true, | ||
"forin": true, | ||
"freeze": true, | ||
"latedef": "nofunc", | ||
"maxcomplexity": 10, | ||
"strict": "global", | ||
"undef": true, | ||
"unused": true, | ||
"globals": { | ||
"chrome": false, | ||
"chai": false, | ||
"sinon": false, | ||
"JSON": false | ||
}, | ||
"browser": true, | ||
"browserify": true, | ||
"mocha": true, | ||
"phantom": true, | ||
"predef": [ | ||
"assert", | ||
"after", | ||
"afterEach", | ||
"before", | ||
"beforeEach", | ||
"describe", | ||
"it", | ||
"require", | ||
"sinon" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
output-format: grouped | ||
strictness: veryhigh | ||
doc-warnings: true | ||
max-line-length: 79 | ||
pep8: | ||
full: true | ||
pylint: | ||
enable: | ||
- relative-import | ||
disable: | ||
- line-too-long # PEP8 checks this and doesn't complain about | ||
# unavoidable long lines (such as URLs). | ||
- R0903 # Too few public methods | ||
- W0142 # Used * or ** magic | ||
options: | ||
# Some good names that pylint would otherwise reject: | ||
# | ||
# - _: placeholder | ||
# - i,j,k: counters | ||
# - k,v: dict iteration | ||
# - db,fn: common abbreviations | ||
# - fp: python idiom for file handles | ||
# | ||
# Some good "constant" names that pylint would otherwise reject: | ||
# | ||
# - log: common in "log = logging.getLogger(__name__)" pattern | ||
# - parser: common in modules that use argparse | ||
# - id: Commonly used as a class attribute / database column name in | ||
# sqlalchemy model classes. Note that if you use id as the name of | ||
# a local variable or parameter, pylint will still complain that | ||
# you're shadowing the builtin. | ||
# | ||
good-names: _,i,j,k,v,e,db,fn,fp,log,parser,id | ||
pep257: | ||
disable: | ||
- D100 # Missing docstring in public module | ||
- D101 # Missing docstring in public class | ||
- D102 # Missing docstring in public method | ||
- D103 # Missing docstring in public function | ||
pyroma: | ||
run: true | ||
ignore-patterns: | ||
- '.*\.egg' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
sudo: false | ||
language: | ||
- python | ||
python: | ||
- "3.5" | ||
install: | ||
- make deps | ||
- pip install coveralls | ||
script: | ||
- make test | ||
after_success: | ||
- coveralls |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
0.0.1 | ||
===== | ||
|
||
- First release, working proof of concept. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
For now, see the `hypothesis/h contributing guide <https://github.com/hypothesis/h/blob/master/CONTRIBUTING.rst>`_. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
FROM frolvlad/alpine-python3 | ||
MAINTAINER Hypothes.is Project and contributors | ||
|
||
# Install system build and runtime dependencies. | ||
RUN apk add --update \ | ||
nodejs \ | ||
&& rm -rf /var/cache/apk/* | ||
|
||
# Create the bouncer user, group, home directory and package directory. | ||
RUN addgroup -S bouncer \ | ||
&& adduser -S -G bouncer -h /var/lib/bouncer bouncer | ||
WORKDIR /var/lib/bouncer | ||
|
||
# Copy packaging | ||
COPY README.rst package.json setup.* ./ | ||
|
||
# Install application dependencies. | ||
RUN npm install --production \ | ||
&& pip install --no-cache-dir -e . \ | ||
&& npm cache clean | ||
|
||
# Copy the rest of the application files | ||
COPY bouncer ./bouncer/ | ||
|
||
# Persist the static directory. | ||
VOLUME ["/var/lib/bouncer/bouncer/static"] | ||
|
||
# Start the web server by default | ||
USER bouncer | ||
CMD ["gunicorn", "bouncer:app()"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
Copyright (c) 2016 Hypothes.is Project and contributors | ||
|
||
Redistribution and use in source and binary forms, with or without | ||
modification, are permitted provided that the following conditions are met: | ||
|
||
1. Redistributions of source code must retain the above copyright notice, this | ||
list of conditions and the following disclaimer. | ||
2. Redistributions in binary form must reproduce the above copyright notice, | ||
this list of conditions and the following disclaimer in the documentation | ||
and/or other materials provided with the distribution. | ||
|
||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
deps: | ||
pip install --upgrade pip | ||
pip install --upgrade wheel | ||
pip install -e .[dev] | ||
npm install | ||
|
||
dev: | ||
PYRAMID_RELOAD_TEMPLATES=1 gunicorn --reload "bouncer:app()" | ||
|
||
docker: | ||
docker build -t hypothesis/bouncer . | ||
|
||
test: | ||
py.test --cov=bouncer bouncer | ||
./node_modules/karma/bin/karma start karma.config.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
.. image:: https://travis-ci.org/hypothesis/bouncer.svg?branch=master | ||
:target: https://travis-ci.org/hypothesis/bouncer | ||
:alt: Build Status | ||
|
||
|
||
Hypothesis Direct-Link Bouncer Service | ||
====================================== | ||
|
||
Production Deployment | ||
--------------------- | ||
|
||
Requirements: | ||
|
||
* `Docker <https://www.docker.com/>`_ | ||
* `Git <https://git-scm.com/>`_ | ||
* `Make <https://www.gnu.org/software/make/>`_ | ||
|
||
Build the ``hypothesis/bouncer`` Docker image and run bouncer in a Docker | ||
container: | ||
|
||
.. code-block:: bash | ||
git clone https://github.com/hypothesis/bouncer.git | ||
cd bouncer | ||
make docker | ||
docker run --net host -p 8000:8000 hypothesis/bouncer | ||
Development | ||
----------- | ||
|
||
Requirements: | ||
|
||
* `Git <https://git-scm.com/>`_ | ||
* `Make <https://www.gnu.org/software/make/>`_ | ||
* `Python 3.5 <https://www.python.org/>`_ | ||
* `Virtualenv <https://virtualenv.readthedocs.org/>`_ | ||
|
||
Install: | ||
|
||
.. code-block:: bash | ||
git clone https://github.com/hypothesis/bouncer.git | ||
cd bouncer | ||
virtualenv -p python3.5 . | ||
. bin/activate | ||
make deps | ||
Run the tests: | ||
|
||
.. code-block:: bash | ||
make test | ||
To debug the JavaScript tests in a browser, run: | ||
|
||
.. code-block:: bash | ||
./node_modules/karma/bin/karma start --no-single-rin | ||
and open http://localhost:9876/ in your browser. | ||
|
||
To run a dev instance on port 8000: | ||
|
||
.. code-block:: bash | ||
export CHROME_EXTENSION_ID=<id_of_your_local_dev_build_of_the_hypothesis_chrome_extension> | ||
make dev | ||
Other environment variables can be used to change other settings, see | ||
`__init__.py <bouncer/__init__.py>`_. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import os | ||
|
||
import pyramid.config | ||
|
||
|
||
def settings(): | ||
""" | ||
Return the app's configuration settings as a dict. | ||
Settings are read from environment variables and fall back to hardcoded | ||
defaults if those variables aren't defined. | ||
""" | ||
via_base_url = os.environ.get("VIA_BASE_URL", "https://via.hypothes.is") | ||
if via_base_url.endswith("/"): | ||
via_base_url = via_base_url[:-1] | ||
|
||
return { | ||
"chrome_extension_id": os.environ.get( | ||
"CHROME_EXTENSION_ID", | ||
"bjfhmglciegochdpefhhlphglcehbmek"), | ||
"elasticsearch_host": os.environ.get("ELASTICSEARCH_HOST", | ||
"localhost"), | ||
"elasticsearch_index": os.environ.get("ELASTICSEARCH_INDEX", | ||
"annotator"), | ||
"elasticsearch_port": os.environ.get("ELASTICSEARCH_PORT", | ||
"9200"), | ||
"hypothesis_url": os.environ.get("HYPOTHESIS_URL", | ||
"https://hypothes.is"), | ||
"via_base_url": via_base_url, | ||
} | ||
|
||
|
||
def app(): | ||
"""Configure and return the WSGI app.""" | ||
config = pyramid.config.Configurator(settings=settings()) | ||
config.add_static_view(name="static", path="static") | ||
config.include("pyramid_jinja2") | ||
config.registry.settings["jinja2.filters"] = { | ||
"static_url": "pyramid_jinja2.filters:static_url_filter" | ||
} | ||
config.include("bouncer.views") | ||
return config.make_wsgi_app() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
'use strict'; | ||
|
||
/** The ID of the Chrome extension we want to talk to. */ | ||
var EDITOR_EXTENSION_ID = 'oldbkmekfdjiffgkconlamcngmkioffd'; | ||
|
||
/** Return the settings object that the server injected into the page. */ | ||
function getSettings(document) { | ||
return JSON.parse( | ||
document.querySelector('script.js-bouncer-settings').textContent); | ||
} | ||
|
||
/** Navigate the browser to the given URL. */ | ||
function navigateTo(url) { | ||
window.location = url; | ||
} | ||
|
||
/** Navigate the browser to the requested annotation. | ||
* | ||
* If the browser is Chrome and our Chrome extension is installed then | ||
* navigate to the annotation's direct link for the Chrome extension. | ||
* If the Chrome extension isn't installed or the browser isn't Chrome then | ||
* navigate to the annotation's Via direct link. | ||
* | ||
*/ | ||
function redirect(navigateToFn) { | ||
// Use the test navigateTo() function if one was passed in, the real | ||
// navigateTo() otherwise. | ||
navigateTo = navigateToFn || navigateTo; | ||
|
||
var settings = getSettings(document); | ||
|
||
if (window.chrome && chrome.runtime && chrome.runtime.sendMessage) { | ||
// The user is using Chrome, redirect them to our Chrome extension if they | ||
// have it installed, via otherwise. | ||
chrome.runtime.sendMessage( | ||
EDITOR_EXTENSION_ID, | ||
{type: 'ping'}, | ||
function (response) { | ||
var url; | ||
if (response) { | ||
// The user has our Chrome extension installed :) | ||
url = settings.extensionUrl; | ||
} else { | ||
// The user doesn't have our Chrome extension installed :( | ||
url = settings.viaUrl; | ||
} | ||
navigateTo(url); | ||
} | ||
); | ||
} else { | ||
// The user isn't using Chrome, just redirect them to Via. | ||
navigateTo(settings.viaUrl); | ||
} | ||
} | ||
|
||
if (typeof module === 'object') { | ||
// Browserify is present, this file must be being run by the tests. | ||
module.exports = redirect; | ||
} else { | ||
// Browserify is not present, this file must be being run in development or | ||
// production. | ||
redirect(); | ||
} |
Oops, something went wrong.