├── .dockerignore
├── .editorconfig
├── .gitignore
├── .jshintignore
├── .jshintrc
├── .yo-rc.json
├── Dockerfile
├── Gruntfile.js
├── LICENSE
├── README.md
├── bower.json
├── client
├── lbclient
│ ├── .gitignore
│ ├── boot
│ │ └── replication.js
│ ├── build.js
│ ├── datasources.json
│ ├── datasources.local.js
│ ├── lbclient.js
│ ├── model-config.json
│ ├── models
│ │ ├── country.js
│ │ ├── country.json
│ │ ├── http-requests-interesting.js
│ │ ├── http-requests-interesting.json
│ │ ├── report.js
│ │ └── report.json
│ └── package.json
└── ngapp
│ ├── build
│ └── main.js.map
│ ├── config
│ ├── bundle.js
│ └── routes.json
│ ├── data
│ ├── bluecoat.json
│ ├── privoxy.json
│ └── squid.json
│ ├── favicon.ico
│ ├── favicons
│ ├── android-icon-144x144.png
│ ├── android-icon-192x192.png
│ ├── android-icon-36x36.png
│ ├── android-icon-48x48.png
│ ├── android-icon-72x72.png
│ ├── android-icon-96x96.png
│ ├── apple-icon-114x114.png
│ ├── apple-icon-120x120.png
│ ├── apple-icon-144x144.png
│ ├── apple-icon-152x152.png
│ ├── apple-icon-180x180.png
│ ├── apple-icon-57x57.png
│ ├── apple-icon-60x60.png
│ ├── apple-icon-72x72.png
│ ├── apple-icon-76x76.png
│ ├── apple-icon-precomposed.png
│ ├── apple-icon.png
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon-96x96.png
│ ├── ms-icon-144x144.png
│ ├── ms-icon-150x150.png
│ ├── ms-icon-310x310.png
│ └── ms-icon-70x70.png
│ ├── images
│ ├── explorer-logo-200.png
│ ├── explorer-logo-24.png
│ ├── explorer-logo-48.png
│ ├── explorer-logo-64.png
│ ├── german-federal-foreign-office-logo.png
│ └── ooni-loader.svg
│ ├── index.html
│ ├── robots.txt
│ ├── scripts
│ ├── app.js
│ ├── controllers
│ │ ├── country.js
│ │ ├── explore.js
│ │ ├── highlights.js
│ │ ├── measurement.js
│ │ ├── nettests.js
│ │ ├── nettests
│ │ │ └── nettests.js
│ │ ├── website.js
│ │ └── world.js
│ ├── directives
│ │ └── directives.js
│ └── services
│ │ ├── country.js
│ │ └── lbclient.js
│ ├── styles
│ ├── _base.scss
│ ├── _country.scss
│ ├── _explore.scss
│ ├── _highlights.scss
│ ├── _loading.scss
│ ├── _measurement.scss
│ ├── _mixins.scss
│ ├── _world.scss
│ ├── fonts
│ │ ├── charter-bold-italic.woff
│ │ ├── charter-bold.woff
│ │ ├── charter-italic.woff
│ │ ├── charter-regular.woff
│ │ ├── fira-sans-bold.otf
│ │ ├── fira-sans-light.otf
│ │ ├── fira-sans-semi-bold.otf
│ │ ├── ooni-icons.eot
│ │ ├── ooni-icons.svg
│ │ ├── ooni-icons.ttf
│ │ ├── ooni-icons.woff
│ │ ├── source-code-pro-bold.woff
│ │ └── source-code-pro-regular.woff
│ ├── main.scss
│ ├── ui-grid.ttf
│ ├── ui-grid.woff
│ └── ui
│ │ ├── _buttons.scss
│ │ ├── _fonts.scss
│ │ ├── _forms.scss
│ │ ├── _icons.scss
│ │ ├── _more-info-hover.scss
│ │ ├── _pagination.scss
│ │ ├── _paragraph.scss
│ │ ├── _section.scss
│ │ ├── _table-of-contents.scss
│ │ ├── _table.scss
│ │ └── _ui-grid.scss
│ └── views
│ ├── about.html
│ ├── country-view.html
│ ├── directives
│ ├── ooni-country-bar-chart.directive.html
│ ├── ooni-explorer-list-measurement.directive.html
│ ├── ooni-filter-list-form.directive.html
│ ├── ooni-grid-wrapper-directive.html
│ ├── ooni-info-country-list.directive.html
│ ├── ooni-info-explorer-list.directive.html
│ ├── ooni-loader.directive.html
│ ├── ooni-more-info-hover-directive.html
│ ├── ooni-pagination.directive.html
│ ├── ooni-report-detail-table-row.html
│ └── row-template.html
│ ├── explore.html
│ ├── highlights.html
│ ├── nettests
│ ├── bridge-reachability.html
│ ├── bridget.html
│ ├── captive-portal.html
│ ├── dns-consistency.html
│ ├── dns-injection.html
│ ├── dns-spoof.html
│ ├── http-header-field-manipulation.html
│ ├── http-host.html
│ ├── http-invalid-request-line.html
│ ├── http-requests.html
│ ├── lantern-circumvention-tool-test.html
│ ├── meek-fronted-requests-test.html
│ ├── multi-protocol-traceroute.html
│ ├── nettest.html
│ ├── openvpn.html
│ ├── psiphon-test.html
│ ├── tcp-connect.html
│ └── web-connectivity.html
│ ├── view-measurement.html
│ ├── website-view.html
│ └── world.html
├── common
└── models
│ ├── censorship_method.js
│ ├── censorship_method.json
│ ├── country.js
│ ├── country.json
│ ├── nettest.js
│ ├── nettest.json
│ ├── report.js
│ └── report.json
├── deploy.sh
├── docs
└── deployment.md
├── global-config.js
├── install.sh
├── ooni-api-spec.json
├── package.json
├── server.js
├── server
├── boot
│ ├── angular-routes.js
│ ├── authentication.js
│ ├── create-default-tables.js
│ ├── dev-assets.js
│ ├── explorer.js
│ ├── extend-models.js
│ └── rest-api.js
├── config.json
├── config.local.js
├── config.production.json
├── datasources.development.js
├── datasources.json
├── datasources.production.js
├── middleware.json
├── model-config.json
├── models
│ ├── country.js
│ └── report.js
├── providers.json
└── server.js
└── yarn.lock
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | bower_components
3 | npm-debug.log
4 | yarn-error.log
5 | ignore
6 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # http://editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 | indent_style = space
9 | indent_size = 2
10 | end_of_line = lf
11 | charset = utf-8
12 | trim_trailing_whitespace = true
13 | insert_final_newline = true
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.csv
2 | *.dat
3 | *.iml
4 | *.log
5 | *.out
6 | *.pid
7 | *.seed
8 | *.sublime-*
9 | *.swo
10 | *.swp
11 | *.tgz
12 | *.xml
13 | .DS_Store
14 | .idea
15 | .project
16 | .strong-pm
17 | coverage
18 | node_modules
19 | npm-debug.log
20 | bower_components
21 | client/dist
22 | .tmp
23 | server/providers-private.json
24 | client/ngapp/styles/main.css
25 | client/ngapp/styles/main.css.map
26 | client/ngapp/build/main.js
27 | client/ngapp/data/factbook
28 | ignore/
29 |
--------------------------------------------------------------------------------
/.jshintignore:
--------------------------------------------------------------------------------
1 | /client/
2 | /node_modules/
3 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "esnext": true,
4 | "bitwise": true,
5 | "camelcase": false,
6 | "eqeqeq": true,
7 | "eqnull": true,
8 | "immed": true,
9 | "indent": 2,
10 | "latedef": "nofunc",
11 | "newcap": true,
12 | "nonew": true,
13 | "noarg": true,
14 | "quotmark": false,
15 | "regexp": true,
16 | "undef": true,
17 | "unused": false,
18 | "trailing": true,
19 | "sub": true,
20 | "maxlen": false,
21 | "asi": true,
22 | "predef": [
23 | "angular",
24 | "colorbrewer"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/.yo-rc.json:
--------------------------------------------------------------------------------
1 | {
2 | "generator-loopback": {}
3 | }
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Best practices for development, and not for a production deployment
2 | # from https://nodejs.org/en/docs/guides/nodejs-docker-webapp/
3 |
4 | # Build: run ooni-sysadmin.git/scripts/docker-build from this directory
5 |
6 | FROM node:carbon
7 |
8 | # BEGIN root
9 | USER root
10 | COPY . /usr/src/app
11 | RUN set -ex \
12 | && yarn add global grunt-cli \
13 | && yarn global add loopback-sdk-angular-cli \
14 | && chown -R node:node /usr/src/app \
15 | && :
16 | # END root
17 |
18 | USER node
19 | WORKDIR /usr/src/app
20 |
21 | # .cache removal leads to two times smaller image and
22 | RUN set -ex \
23 | && yarn install --frozen-lockfile \
24 | && npm run build \
25 | && rm -rf /home/node/.cache \
26 | && :
27 |
28 | EXPOSE 3000
29 |
30 | USER daemon
31 | CMD [ "npm", "start" ]
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2017 Open Observatory of Network Interference (OONI), The Tor Project
2 |
3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4 |
5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6 |
7 | 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.
8 |
9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10 |
11 | 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 HOLDER 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.
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # OONI Explorer Legacy [DEPRECATED]
2 |
3 | For the latest and greatest OONI Explorer code, see: https://github.com/ooni/explorer
4 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ooni-explorer",
3 | "version": "0.0.0",
4 | "license": "BSD-2-Clause",
5 | "private": true,
6 | "appPath": "client/ngapp",
7 | "testPath": "client/ngapp/test/spec",
8 | "ignore": [
9 | "**/.*",
10 | "node_modules",
11 | "**/bower_components",
12 | "test",
13 | "tests"
14 | ],
15 | "dependencies": {
16 | "angular": "~1.4",
17 | "json3": "~3.3.1",
18 | "es5-shim": "~3.1.0",
19 | "angular-route": "~1.4",
20 | "angular-resource": "~1.4",
21 | "topojson": "topojson#1.6.20",
22 | "angular-datamaps": "~0.1.0",
23 | "colorbrewer": "~1.0.0",
24 | "angular-typewrite": "~0.0.14",
25 | "flag-icon-css": "",
26 | "angular-ui-grid": "~3.0.0-rc.21",
27 | "font-awesome": "fontawesome#~4.5.0",
28 | "iso-3166-country-codes-angular": "https://github.com/hellais/iso-3166-country-codes-angular/archive/611d0cf4dc2244a74cd337c516744d1dce235e7c.zip",
29 | "json-formatter": "~0.4.2",
30 | "factbook-country-data": "https://github.com/simonv3/factbook-country-data/archive/0cceccd6e393e6a860798e0b7e5be28c4f18efd4.zip",
31 | "angular-daterangepicker": "~0.2.2",
32 | "angular-ui-codemirror": "~0.3.0",
33 | "angular-inview": "~1.5.6"
34 | },
35 | "devDependencies": {
36 | "angular-mocks": "~1.4",
37 | "angular-scenario": "~1.4"
38 | },
39 | "resolutions": {
40 | "angular": "1.4"
41 | },
42 | "overrides": {
43 | "font-awesome": {
44 | "main": [
45 | "css/font-awesome.css"
46 | ]
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/client/lbclient/.gitignore:
--------------------------------------------------------------------------------
1 | browser.bundle.js
2 |
--------------------------------------------------------------------------------
/client/lbclient/boot/replication.js:
--------------------------------------------------------------------------------
1 | // TODO figure out what this is supposed to be
2 |
--------------------------------------------------------------------------------
/client/lbclient/build.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var pkg = require('./package.json');
3 | var fs = require('fs');
4 | var browserify = require('browserify');
5 | var boot = require('loopback-boot');
6 |
7 | module.exports = function buildBrowserBundle(env, callback) {
8 | var b = browserify({ basedir: __dirname });
9 | b.require('./' + pkg.main, { expose: 'lbclient' });
10 |
11 | try {
12 | boot.compileToBrowserify({
13 | appRootDir: __dirname,
14 | env: env
15 | }, b);
16 | } catch(err) {
17 | return callback(err);
18 | }
19 |
20 | var bundlePath = path.resolve(__dirname, 'browser.bundle.js');
21 | var out = fs.createWriteStream(bundlePath);
22 | var isDevEnv = ~['debug', 'development', 'test'].indexOf(env);
23 |
24 | b.bundle({
25 | // TODO(bajtos) debug should be always true, the sourcemaps should be
26 | // saved to a standalone file when !isDev(env)
27 | debug: isDevEnv
28 | })
29 | .on('error', callback)
30 | .pipe(out);
31 |
32 | out.on('error', callback);
33 | out.on('close', callback);
34 | };
35 |
--------------------------------------------------------------------------------
/client/lbclient/datasources.json:
--------------------------------------------------------------------------------
1 | {
2 | "remote": {
3 | "connector": "remote"
4 | },
5 | "local": {
6 | "connector": "memory"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/client/lbclient/datasources.local.js:
--------------------------------------------------------------------------------
1 | var GLOBAL_CONFIG = require('../../global-config');
2 |
3 | module.exports = {
4 | remote: {
5 | url: GLOBAL_CONFIG.restApiUrl
6 | }
7 | };
8 |
--------------------------------------------------------------------------------
/client/lbclient/model-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "sources": ["../../common/models", "./models"]
4 | },
5 | "RemoteReport": {
6 | "dataSource": "remote"
7 | },
8 | "RemoteHttpRequestsInteresting": {
9 | "dataSource": "remote"
10 | },
11 | "RemoteCountry": {
12 | "dataSource": "remote"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/client/lbclient/models/country.js:
--------------------------------------------------------------------------------
1 | module.exports = function(Country) {
2 | var app = require('lbclient');
3 |
4 | Country.prototype.getCountryInfo = function(country_code, callback) {
5 | app.models().country.getCountryInfo(country_code, callback);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/client/lbclient/models/country.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "RemoteCountry",
3 | "base": "country",
4 | "plural": "countries",
5 | "trackChanges": false,
6 | "enableRemoteReplication": true
7 | }
8 |
--------------------------------------------------------------------------------
/client/lbclient/models/http-requests-interesting.js:
--------------------------------------------------------------------------------
1 | module.exports = function(HttpRequestsInteresting) {
2 | HttpRequestsInteresting.findInteresting = function(country_code, fields, limit, callback) {
3 | HttpRequestsInteresting.app.models.RemoteHttpRequestsInteresting.findInteresting(country_code, fields, limit, callback)
4 | }
5 |
6 | HttpRequestsInteresting.listInteresting = function(key, callback) {
7 | HttpRequestsInteresting.app.models.RemoteHttpRequestsInteresting.find(key, callback);
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/client/lbclient/models/http-requests-interesting.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "RemoteHttpRequestsInteresting",
3 | "base": "httpRequestsInteresting",
4 | "plural": "httpRequestsInterestings",
5 | "trackChanges": false,
6 | "enableRemoteReplication": true
7 | }
8 |
--------------------------------------------------------------------------------
/client/lbclient/models/report.js:
--------------------------------------------------------------------------------
1 | module.exports = function(Report) {
2 | }
3 |
--------------------------------------------------------------------------------
/client/lbclient/models/report.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "RemoteReport",
3 | "base": "report",
4 | "plural": "reports",
5 | "trackChanges": false,
6 | "enableRemoteReplication": true
7 | }
8 |
--------------------------------------------------------------------------------
/client/lbclient/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "main": "lbclient.js"
4 | }
5 |
--------------------------------------------------------------------------------
/client/ngapp/config/bundle.js:
--------------------------------------------------------------------------------
1 | window.CONFIG = {
2 | "routes": {
3 | "/highlights/": {
4 | "controller": "HighlightsCtrl",
5 | "templateUrl": "/views/highlights.html"
6 | },
7 | "/about/": {
8 | "controller": "HighlightsCtrl",
9 | "templateUrl": "/views/about.html"
10 | },
11 | "/world/": {
12 | "controller": "WorldCtrl",
13 | "templateUrl": "/views/world.html"
14 | },
15 | "/explore/": {
16 | "controller": "ExploreViewCtrl",
17 | "templateUrl": "/views/explore.html"
18 | },
19 | "/measurement/:id": {
20 | "controller": "MeasurementDetailViewCtrl",
21 | "templateUrl": "/views/view-measurement.html"
22 | },
23 | "/country/:id": {
24 | "controller": "CountryDetailViewCtrl",
25 | "templateUrl": "/views/country-view.html"
26 | },
27 | "/website/:id*": {
28 | "controller": "WebsiteDetailViewCtrl",
29 | "templateUrl": "/views/website-view.html"
30 | }
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/client/ngapp/config/routes.json:
--------------------------------------------------------------------------------
1 | {
2 | "/highlights/": {
3 | "controller": "HighlightsCtrl",
4 | "templateUrl": "/views/highlights.html"
5 | },
6 | "/about/": {
7 | "controller": "HighlightsCtrl",
8 | "templateUrl": "/views/about.html"
9 | },
10 | "/world/": {
11 | "controller": "WorldCtrl",
12 | "templateUrl": "/views/world.html"
13 | },
14 | "/explore/": {
15 | "controller": "ExploreViewCtrl",
16 | "templateUrl": "/views/explore.html"
17 | },
18 | "/measurement/:id": {
19 | "controller": "MeasurementDetailViewCtrl",
20 | "templateUrl": "/views/view-measurement.html"
21 | },
22 | "/country/:id": {
23 | "controller": "CountryDetailViewCtrl",
24 | "templateUrl": "/views/country-view.html"
25 | },
26 | "/website/:id*": {
27 | "controller": "WebsiteDetailViewCtrl",
28 | "templateUrl": "/views/website-view.html"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/client/ngapp/data/bluecoat.json:
--------------------------------------------------------------------------------
1 | {
2 | "vendor":"Bluecoat"
3 | }
4 |
--------------------------------------------------------------------------------
/client/ngapp/data/privoxy.json:
--------------------------------------------------------------------------------
1 | {
2 | "vendor":"Privoxy"
3 | }
4 |
--------------------------------------------------------------------------------
/client/ngapp/data/squid.json:
--------------------------------------------------------------------------------
1 | {
2 | "vendor":"Squid"
3 | }
4 |
--------------------------------------------------------------------------------
/client/ngapp/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/favicon.ico
--------------------------------------------------------------------------------
/client/ngapp/favicons/android-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/favicons/android-icon-144x144.png
--------------------------------------------------------------------------------
/client/ngapp/favicons/android-icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/favicons/android-icon-192x192.png
--------------------------------------------------------------------------------
/client/ngapp/favicons/android-icon-36x36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/favicons/android-icon-36x36.png
--------------------------------------------------------------------------------
/client/ngapp/favicons/android-icon-48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/favicons/android-icon-48x48.png
--------------------------------------------------------------------------------
/client/ngapp/favicons/android-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/favicons/android-icon-72x72.png
--------------------------------------------------------------------------------
/client/ngapp/favicons/android-icon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/favicons/android-icon-96x96.png
--------------------------------------------------------------------------------
/client/ngapp/favicons/apple-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/favicons/apple-icon-114x114.png
--------------------------------------------------------------------------------
/client/ngapp/favicons/apple-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/favicons/apple-icon-120x120.png
--------------------------------------------------------------------------------
/client/ngapp/favicons/apple-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/favicons/apple-icon-144x144.png
--------------------------------------------------------------------------------
/client/ngapp/favicons/apple-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/favicons/apple-icon-152x152.png
--------------------------------------------------------------------------------
/client/ngapp/favicons/apple-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/favicons/apple-icon-180x180.png
--------------------------------------------------------------------------------
/client/ngapp/favicons/apple-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/favicons/apple-icon-57x57.png
--------------------------------------------------------------------------------
/client/ngapp/favicons/apple-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/favicons/apple-icon-60x60.png
--------------------------------------------------------------------------------
/client/ngapp/favicons/apple-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/favicons/apple-icon-72x72.png
--------------------------------------------------------------------------------
/client/ngapp/favicons/apple-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/favicons/apple-icon-76x76.png
--------------------------------------------------------------------------------
/client/ngapp/favicons/apple-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/favicons/apple-icon-precomposed.png
--------------------------------------------------------------------------------
/client/ngapp/favicons/apple-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/favicons/apple-icon.png
--------------------------------------------------------------------------------
/client/ngapp/favicons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/favicons/favicon-16x16.png
--------------------------------------------------------------------------------
/client/ngapp/favicons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/favicons/favicon-32x32.png
--------------------------------------------------------------------------------
/client/ngapp/favicons/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/favicons/favicon-96x96.png
--------------------------------------------------------------------------------
/client/ngapp/favicons/ms-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/favicons/ms-icon-144x144.png
--------------------------------------------------------------------------------
/client/ngapp/favicons/ms-icon-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/favicons/ms-icon-150x150.png
--------------------------------------------------------------------------------
/client/ngapp/favicons/ms-icon-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/favicons/ms-icon-310x310.png
--------------------------------------------------------------------------------
/client/ngapp/favicons/ms-icon-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/favicons/ms-icon-70x70.png
--------------------------------------------------------------------------------
/client/ngapp/images/explorer-logo-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/images/explorer-logo-200.png
--------------------------------------------------------------------------------
/client/ngapp/images/explorer-logo-24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/images/explorer-logo-24.png
--------------------------------------------------------------------------------
/client/ngapp/images/explorer-logo-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/images/explorer-logo-48.png
--------------------------------------------------------------------------------
/client/ngapp/images/explorer-logo-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/images/explorer-logo-64.png
--------------------------------------------------------------------------------
/client/ngapp/images/german-federal-foreign-office-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/images/german-federal-foreign-office-logo.png
--------------------------------------------------------------------------------
/client/ngapp/images/ooni-loader.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/client/ngapp/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | OONI Explorer
6 |
7 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
47 |
48 |
49 |
50 |
51 |
54 |
70 |
73 |
74 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/client/ngapp/robots.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/robots.txt
--------------------------------------------------------------------------------
/client/ngapp/scripts/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @ngdoc overview
5 | * @name ooniAPIApp
6 | * @description
7 | *
8 | * Main module of the application.
9 | */
10 | angular
11 | .module('ooniAPIApp', [
12 | 'ngRoute',
13 | 'lbServices',
14 | 'ngResource',
15 | 'datamaps',
16 | 'angularTypewrite',
17 | 'ui.grid',
18 | 'ui.grid.pagination',
19 | 'ui.codemirror',
20 | 'iso-3166-country-codes',
21 | 'jsonFormatter',
22 | 'daterangepicker',
23 | 'angular-inview'
24 | ])
25 | .config(function ($routeProvider, $locationProvider) {
26 | Object.keys(window.CONFIG.routes)
27 | .forEach(function(route) {
28 | var routeDef = window.CONFIG.routes[route];
29 | $routeProvider.when(route, routeDef);
30 | });
31 |
32 | $routeProvider
33 | .otherwise({
34 | redirectTo: '/world'
35 | });
36 |
37 | $locationProvider.html5Mode(true);
38 | })
39 | // Things to run before the app loads;
40 | .run(function ($rootScope, $location, $anchorScroll) {
41 | $rootScope.$location = $location;
42 | });
43 |
--------------------------------------------------------------------------------
/client/ngapp/scripts/controllers/country.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @ngdoc function
5 | * @name ooniAPIApp.controller:CountryViewCtrl
6 | * @description
7 | * # CountryViewCtrl
8 | * Controller of the ooniAPIApp
9 | */
10 |
11 | angular.module('ooniAPIApp')
12 | .controller('CountryDetailViewCtrl', function ($q, $scope, $rootScope, $filter, Report, $http, $routeParams, ISO3166, Country) {
13 | $scope.loaded = false;
14 |
15 | $scope.countryCode = $routeParams.id;
16 | $scope.countryName = ISO3166.getCountryName($scope.countryCode);
17 | $scope.encodeInput = window.encodeURIComponent;
18 |
19 | $http.get('data/factbook/' + $scope.countryCode.toLowerCase() + '.json')
20 | .then(function(response) {
21 | $scope.countryDetails = response.data;
22 | }, function(error) {
23 | console.log('error', error)
24 | })
25 |
26 | Country.findOne({
27 | filter: {
28 | where: {iso_alpha2: $scope.countryCode},
29 | include: ['censorship_methods']
30 | }
31 | }, function(response) {
32 | $scope.censorshipMethods = response.censorship_methods;
33 | });
34 |
35 | Report.blockpageCount({probe_cc: $scope.countryCode}, function(resp) {
36 | // this goes off and gets processed by the bar-chart directive
37 | $scope.blockpageCount = resp;
38 | });
39 |
40 | Report.blockpageList({probe_cc: $scope.countryCode}, function(resp) {
41 | $scope.blockpageList = resp;
42 |
43 | $scope.chunkedBlockpageList = {}
44 |
45 | resp.forEach(function(page) {
46 | if ($scope.chunkedBlockpageList[page.input] === undefined) {
47 | $scope.chunkedBlockpageList[page.input] = {
48 | measurements: [page]
49 | }
50 | } else {
51 | $scope.chunkedBlockpageList[page.input].measurements.push(page)
52 | }
53 | });
54 |
55 | $scope.chunkedArray = [];
56 |
57 | angular.forEach($scope.chunkedBlockpageList, function(val, key) {
58 | val.input = key;
59 | $scope.chunkedArray.push(val)
60 | })
61 |
62 | $scope.loadedChunks = $scope.chunkedArray.slice(0, 10)
63 | });
64 |
65 | var loadingMore = false;
66 | var chunkLength = 50;
67 |
68 | $scope.loadMoreChunks = function() {
69 | if ($scope.chunkedArray && !loadingMore) {
70 | loadingMore = true;
71 | var len = $scope.loadedChunks.length;
72 | var next = $scope.chunkedArray.slice(len, len + chunkLength)
73 | $scope.loadedChunks = $scope.loadedChunks.concat(next)
74 | if (next.length < chunkLength) {
75 | $scope.chunkEndReached = true;
76 | }
77 | }
78 | loadingMore = false;
79 | }
80 |
81 | Report.vendors( {probe_cc: $scope.countryCode}, function(resp) {
82 | $scope.vendors = resp;
83 | $scope.vendors.forEach(function(vendor) {
84 |
85 | var url = 'data/' + vendor.vendor + '.json'
86 | $http.get(url)
87 | .then(function(resp) {
88 | vendor.data = resp.data;
89 | }, function(err, resp) {
90 | console.log('err', resp)
91 | })
92 | })
93 | });
94 |
95 | Report.countByCountry(function (result) {
96 | var thisCountry = result.filter(function(item) { return item.alpha2 === $scope.countryCode })
97 | $scope.count = (thisCountry[0] && thisCountry[0].count) || -1
98 | })
99 |
100 | // XXX should use external pagination feature of ui grid
101 | // http://ui-grid.info/docs/#/tutorial/314_external_pagination
102 |
103 | $scope.loadMeasurements = function(queryOptions) {
104 | var deferred = $q.defer();
105 |
106 | queryOptions.where['probe_cc'] = $scope.countryCode;
107 | var query = {
108 | filter: {
109 | fields: {
110 | 'test_name': true,
111 | 'input': true,
112 | 'probe_cc': true,
113 | 'test_start_time': true,
114 | 'id': true,
115 | 'probe_asn': true
116 | },
117 | where: queryOptions.where,
118 | offset: queryOptions.pageNumber * queryOptions.pageSize,
119 | limit: queryOptions.pageSize
120 | }
121 | }
122 | var params = {}
123 |
124 | if (queryOptions.where) {
125 | params.probe_cc = queryOptions.where.probe_cc
126 | params.input = queryOptions.where.input
127 | params.test_name = queryOptions.where.test_name
128 | params.since = queryOptions.where.test_start_time && queryOptions.where.test_start_time.between[0]
129 | params.until = queryOptions.where.test_start_time && queryOptions.where.test_start_time.between[1]
130 | }
131 | params.order = queryOptions.order
132 | params.page_size = queryOptions.pageSize
133 | params.page_number = queryOptions.pageNumber
134 |
135 | Report.findMeasurements(params, function(data) {
136 | deferred.resolve(data);
137 |
138 | $scope.loaded = true;
139 | });
140 |
141 | return deferred.promise;
142 | }
143 |
144 | })
145 |
--------------------------------------------------------------------------------
/client/ngapp/scripts/controllers/explore.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @ngdoc function
5 | * @name ooniAPIApp.controller:ExploreViewCtrl
6 | * @description
7 | * # ExploreViewCtrl
8 | * Controller of the ooniAPIApp
9 | */
10 |
11 | angular.module('ooniAPIApp')
12 | .controller('ExploreViewCtrl', function ($q, $scope, $anchorScroll,
13 | $location, Nettest, Report,
14 | $routeParams, uiGridConstants,
15 | $rootScope) {
16 |
17 | $scope.loadMeasurements = function(queryOptions) {
18 |
19 | $scope.loaded = false;
20 |
21 | var deferred = $q.defer();
22 | var query = {
23 | filter: {
24 | fields: {
25 | 'test_name': true,
26 | 'probe_cc': true,
27 | 'probe_asn': true,
28 | 'input': true,
29 | 'test_start_time': true,
30 | 'id': true
31 | },
32 | where: queryOptions.where,
33 | order: queryOptions.order,
34 | offset: queryOptions.pageNumber * queryOptions.pageSize,
35 | limit: queryOptions.pageSize
36 | }
37 | }
38 | var params = {}
39 |
40 | if (queryOptions.where) {
41 | params.probe_cc = queryOptions.where.probe_cc
42 | params.input = queryOptions.where.input
43 | params.test_name = queryOptions.where.test_name
44 | params.since = queryOptions.where.test_start_time && queryOptions.where.test_start_time.between[0]
45 | params.until = queryOptions.where.test_start_time && queryOptions.where.test_start_time.between[1]
46 | }
47 | params.order = queryOptions.order
48 | params.page_size = queryOptions.pageSize
49 | params.page_number = queryOptions.pageNumber
50 |
51 | Report.total(function (result) {
52 | Report.findMeasurements(params, function(data) {
53 | data.total = result.total;
54 | deferred.resolve(data);
55 |
56 | $scope.loaded = true;
57 | });
58 | })
59 |
60 | return deferred.promise;
61 | }
62 |
63 | });
64 |
--------------------------------------------------------------------------------
/client/ngapp/scripts/controllers/highlights.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @ngdoc function
5 | * @name ooniAPIApp.controller:OverviewCtrl
6 | * @description
7 | * # OverviewCtrl
8 | * Controller of the ooniAPIApp
9 | */
10 |
11 | angular.module('ooniAPIApp')
12 | .controller('HighlightsCtrl', function ($rootScope, $location) {
13 | $rootScope.loaded = true;
14 | });
15 |
--------------------------------------------------------------------------------
/client/ngapp/scripts/controllers/measurement.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @ngdoc function
5 | * @name ooniAPIApp.controller:MeasurementDetailViewCtrl
6 | * @description
7 | * # ReportDetailViewCtrl
8 | * Controller of the ooniAPIApp
9 | */
10 |
11 | angular.module('ooniAPIApp')
12 | .controller('MeasurementDetailViewCtrl', function ($q, $scope, $anchorScroll, $rootScope, $location, $http, Report, Nettest, Country, $routeParams, ISO3166) {
13 |
14 | $scope.measurementId = $routeParams.id;
15 | $scope.measurementInput = $routeParams.input;
16 |
17 | $rootScope.loaded = false;
18 | // XXX should use external pagination feature of ui grid
19 | // http://ui-grid.info/docs/#/tutorial/314_external_pagination
20 | $scope.pageNumber = 0;
21 | $scope.pageSize = 100;
22 | $scope.definitions = definitions;
23 |
24 | function loading_success(data) {
25 | $scope.report = data;
26 | $scope.network_information = $scope.report.probe_asn
27 | Report.asnName({asn: $scope.report.probe_asn}, function(result) {
28 | if (result[0] != undefined) {
29 | $scope.network_information = result[0].name + " ( " + $scope.network_information + " )";
30 | }
31 | });
32 |
33 | $scope.nettest = Nettest.findOne({
34 | filter: {
35 | where: {
36 | name: $scope.report.test_name
37 | }
38 | }
39 | });
40 |
41 | $scope.countryName = ISO3166.getCountryName($scope.report.probe_cc);
42 |
43 | $rootScope.loaded = true;
44 | }
45 |
46 | function loading_failure() {
47 | console.log('failed');
48 | $rootScope.loaded = true;
49 | $scope.not_found = true;
50 | }
51 |
52 | function getMeasurementJson(reportId, input, cb, eb) {
53 | var query = {
54 | report_id: reportId,
55 | }
56 | if (input !== undefined) {
57 | query['input'] = input
58 | }
59 | return Report.findMeasurements(query, function(result) {
60 | $http.get(result[0].measurement_url)
61 | .then(function(response) {
62 | cb(response.data)
63 | }, eb)
64 | }, eb)
65 | }
66 |
67 | $scope.measurement = getMeasurementJson($scope.measurementId, $scope.measurementInput, loading_success, loading_failure)
68 |
69 | });
70 |
71 |
72 | var definitions = {
73 | options: {
74 | description: "A dictionary containing the keys and values of options passed to the test",
75 | },
76 | probe_asn: {
77 | description: "The AS Number of the probe (prefixed by AS, ex. AS1234) or null if includeasn is set to false.",
78 | },
79 | probe_cc: {
80 | description: "The two letter country code of the probe or null if inlcudecc is set to false.",
81 | },
82 | as_number: {
83 | external_url: "https://en.wikipedia.org/wiki/Autonomous_system_%28Internet%29"
84 | },
85 | probe_ip: {
86 | description: "The IPv4 address of the probe or null if includeip is set to false.",
87 | },
88 | software_name: {
89 | description: "The name of the software that has generated such report (ex. ooniprobe).",
90 | },
91 | software_version: {
92 | description: "The version of the software that has generated such report (ex. 0.0.10).",
93 | },
94 | start_time: {
95 | description: "The time at which the test was started in seconds since epoch.",
96 | },
97 | test_name: {
98 | description: "The name of the test that such report is for (ex. HTTP Requests).",
99 | },
100 | test_version: {
101 | description: "The version of the test that such report is for (ex. 0.0.10).",
102 | },
103 | data_format: {
104 | description: "The version string of the data format being used by the test (ex. httpt-000)",
105 | },
106 | report_id: {
107 | description: "A 64 character mixed case string that is generated by the client used to identify the report.",
108 | },
109 | test_helpers: {
110 | description: "A dictionary with as keys the names of the options and values the addresses of the test helpers used",
111 | },
112 | test_input: {
113 | description: "The specific input for this test"
114 | }
115 | }
116 |
117 |
--------------------------------------------------------------------------------
/client/ngapp/scripts/controllers/nettests.js:
--------------------------------------------------------------------------------
1 | angular.module('ooniAPIApp')
2 | .controller('HTTPRequestsViewCtrl', function ($scope, $location){
3 |
4 | $scope.encodeInput = window.encodeURIComponent;
5 |
6 | angular.forEach($scope.report.test_keys.requests, function(request) {
7 | if (request.request.tor === true || request.request.tor.is_tor === true) {
8 | $scope.control = request;
9 | } else {
10 | $scope.experiment = request;
11 | }
12 | });
13 |
14 | $scope.experiment_body = null;
15 | if ($scope.experiment && $scope.experiment.response && $scope.experiment.response.body) {
16 | $scope.experiment_body = $scope.experiment.response.body;
17 | }
18 | $scope.body_length_match = 'unknown';
19 | if ($scope.report.test_keys.body_length_match == true) {
20 | $scope.body_length_match = 'true';
21 | } else if ($scope.report.test_keys.body_length_match == false) {
22 | $scope.body_length_match = 'false';
23 | }
24 |
25 | if (typeof $scope.experiment === 'undefined') {
26 | $scope.experiment_failure = 'unknown';
27 | } else {
28 | $scope.experiment_failure = $scope.experiment.failure || 'none';
29 | }
30 |
31 | if (typeof $scope.control === 'undefined') {
32 | $scope.control_failure = 'unknown';
33 | } else {
34 | $scope.control_failure = $scope.control.failure || 'none';
35 | }
36 |
37 | $scope.anomaly = false;
38 | if ($scope.body_length_match === 'false') {
39 | $scope.anomaly = true;
40 | }
41 | if ($scope.experiment_failure !== 'none' && ($scope.control_failure === 'none' || $scope.control_failure === 'unknown')) {
42 | $scope.anomaly = true;
43 | }
44 | if ($scope.report.test_keys.headers_match == false) {
45 | $scope.anomaly = true;
46 | }
47 |
48 | $scope.header_names = [];
49 | if ($scope.control && $scope.control.response) {
50 | for (var header_name in $scope.control.response.headers) {
51 | if ($scope.header_names.indexOf(header_name) == -1) {
52 | $scope.header_names.push(header_name);
53 | }
54 | }
55 | }
56 |
57 | if ($scope.experiment && $scope.experiment.response) {
58 | for (var header_name in $scope.experiment.response.headers) {
59 | if ($scope.header_names.indexOf(header_name) == -1) {
60 | $scope.header_names.push(header_name);
61 | }
62 | }
63 | }
64 |
65 | $scope.code_mirror_options = {
66 | lineWrapping : true,
67 | mode: 'xml'
68 | }
69 | })
70 | .controller('DNSConsistencyViewCtrl', function ($scope, $location){
71 | })
72 | .controller('WebConnectivityViewCtrl', function ($scope, $location){
73 | $scope.encodeInput = window.encodeURIComponent;
74 |
75 | $scope.code_mirror_options = {
76 | lineWrapping : true,
77 | mode: 'xml'
78 | }
79 | });
80 |
--------------------------------------------------------------------------------
/client/ngapp/scripts/controllers/nettests/nettests.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // The idea behind this file is to keep a place for all specific nettest
4 | // controllers in one place, as long as they don't have any specific
5 | // functionality
6 |
7 | angular.module('ooniAPIApp')
8 | .directive('ooniNettestDetails', function ($location) {
9 | return {
10 | restrict: 'A',
11 | scope: {
12 | report: '='
13 | },
14 | link: function($scope) {
15 |
16 | // Not sure if this is the best way to go about doing this.
17 | // It runs at all times.
18 | $scope.getContentUrl = function() {
19 | var nettestSlug = 'nettest';
20 | if ($scope.report !== undefined) {
21 | nettestSlug = $scope.report.test_name.replace(/_/g, '-');
22 | }
23 | var url = '/views/nettests/' + nettestSlug + '.html';
24 | return url
25 | }
26 | },
27 | template: 'fasdfdas
'
28 | }
29 | })
30 | .directive('ooniNettestSummary', function ($location) {
31 | return {
32 | restrict: 'A',
33 | scope: {
34 | report: '='
35 | },
36 | link: function($scope) {
37 |
38 | // Not sure if this is the best way to go about doing this.
39 | // It runs at all times.
40 | $scope.getContentUrl = function() {
41 | var nettestSlug = 'nettest';
42 | if ($scope.report !== undefined) {
43 | nettestSlug = $scope.report.test_name.replace('_', '-');
44 | }
45 | var url = '/views/nettests/' + nettestSlug + '-summary.html';
46 | return url;
47 | }
48 | },
49 | template: 'Build A Summary Template Directive',
50 | // template: '
'
51 | }
52 | });
53 |
--------------------------------------------------------------------------------
/client/ngapp/scripts/controllers/website.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | /**
4 | * @ngdoc function
5 | * @name ooniAPIApp.controller:WebsiteDetailViewCtrl
6 | * @description
7 | * # WebsiteDetailViewCtrl
8 | * Controller of the ooniAPIApp
9 | */
10 |
11 | angular.module('ooniAPIApp')
12 | .controller('WebsiteDetailViewCtrl', function ($scope, Report, $http, $routeParams, ISO3166) {
13 | $scope.websiteUrl = $routeParams.id
14 | $scope.encodeInput = window.encodeURIComponent;
15 |
16 | Report.websiteMeasurements({website_url: $scope.websiteUrl}, function (resp) {
17 | $scope.measurementsByCountry = {}
18 | resp.forEach(function (measurement) {
19 | if ($scope.measurementsByCountry[measurement.probe_cc] !== undefined) {
20 | $scope.measurementsByCountry[measurement.probe_cc].measurements
21 | .push(measurement)
22 | } else {
23 | $scope.measurementsByCountry[measurement.probe_cc] = {
24 | measurements: [measurement],
25 | country: ISO3166.getCountryName(measurement.probe_cc)
26 | }
27 | }
28 | })
29 | }, function (err) {
30 | if (err) console.log('err', err)
31 | })
32 |
33 | Report.websiteDetails({website_url: $scope.websiteUrl}, function (resp) {
34 | $scope.details = resp[0]
35 | console.log($scope.details)
36 | }, function (err) {
37 | if (err) console.log('err', err)
38 | })
39 |
40 | // var alexaUrl = 'http://data.alexa.com/data?cli=10&data=snbamz&url=' + $scope.websiteUrl
41 | })
42 |
--------------------------------------------------------------------------------
/client/ngapp/scripts/services/country.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/scripts/services/country.js
--------------------------------------------------------------------------------
/client/ngapp/styles/_country.scss:
--------------------------------------------------------------------------------
1 | /* Country */
2 |
3 | .country {
4 | h1 {
5 | margin-bottom: 1rem;
6 | }
7 | }
8 |
9 | .hang-right {
10 | display: inline-block;
11 | }
12 |
13 |
14 | .map {
15 | margin-top: -80px;
16 | }
17 |
18 | .explanation {
19 | margin: 0rem auto 2rem;
20 | }
21 |
22 | .statistics {
23 | margin-top: 1rem;
24 | }
25 |
26 | .graph-description {
27 | text-align: center;
28 | font-size: .8rem;
29 | margin-top: .5rem;
30 |
31 | i {
32 | display: inline-block;
33 | width: 1em;
34 | height: 1em;
35 | }
36 |
37 | .measured {
38 | background: $color-ooni-blue;
39 | }
40 | .blocked {
41 | background: $color-blocked;
42 | }
43 | }
44 |
45 | .bar-chart-wrapper {
46 | text-align: center;
47 |
48 | a:hover {
49 | color: darken($color-ooni-blue, 10%);
50 | cursor: pointer;
51 | }
52 | }
53 |
54 | .bar-chart {
55 |
56 | max-height: 100%;
57 | max-width: 100%;
58 | width: 100%;
59 | height: 100%;
60 |
61 |
62 | .bar {
63 | cursor: pointer;
64 | display: inline-block;
65 | fill: $color-ooni-blue;
66 | margin-right: 2rem;
67 |
68 | &.blocked {
69 | fill: $color-blocked;
70 | }
71 | }
72 |
73 | text {
74 | fill: $color-grey;
75 | opacity: 1;
76 | font-size: 16px;
77 | font-family: "Fira Sans", sans-serif;
78 |
79 | &.date {
80 | text-anchor: start;
81 | font-size: 14px;
82 | }
83 | &.total {
84 | fill: $color-ooni-blue;
85 | text-anchor: middle;
86 | }
87 | &.blocked {
88 | fill: $color-blocked;
89 | text-anchor: start;
90 | }
91 | }
92 |
93 | .hidden {
94 | opacity: 0;
95 | }
96 | }
97 |
98 | .country section {
99 | margin-top: 2em;
100 |
101 | h2 {
102 | margin-bottom: 1em;
103 | }
104 |
105 | .no-show {
106 | background-color: $color-off-white;
107 | padding: 2rem;
108 | text-align: center;
109 | color: $color-grey;
110 | }
111 |
112 | &.websites-blocked {
113 | ul {
114 | font-size: .8rem;
115 | list-style: none;
116 | }
117 |
118 | li {
119 | display: inline-block;
120 | vertical-align: top;
121 | width: 32%;
122 | margin-left: 0;
123 | padding-left: 0;
124 | }
125 | }
126 |
127 | &.vendors {
128 | ul {
129 | list-style: none;
130 |
131 | li {
132 | display: inline-block;
133 | width: 45%;
134 | }
135 | }
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/client/ngapp/styles/_explore.scss:
--------------------------------------------------------------------------------
1 | /* Explore */
2 |
3 | .explore {
4 | .explorer-list {
5 | list-style: none;
6 | margin: 0;
7 |
8 | li {
9 | margin: 0;
10 | padding: .5rem;
11 | word-wrap: break-word;
12 |
13 | &:nth-child(odd) {
14 | background-color: $color-off-white;
15 | }
16 | }
17 | }
18 |
19 | .column {
20 | display: inline-block;
21 | }
22 |
23 | .probe-cc {
24 | width: 9%;
25 | }
26 |
27 | .name {
28 | width: 37%;
29 | }
30 |
31 | .input {
32 | width: 34%;
33 | }
34 |
35 | .probe-asn {
36 | width: 10%;
37 | text-align: right;
38 | }
39 |
40 | .start-time {
41 | width: 10%;
42 | text-align: right;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/client/ngapp/styles/_highlights.scss:
--------------------------------------------------------------------------------
1 | /* Highlights */
2 |
3 | .ooni-highlight {
4 |
5 | padding-left: 20px;
6 | padding-right: 20px;
7 | padding-top: 20px;
8 |
9 | margin-right: 10px;
10 | margin-top: 10px;
11 |
12 | border-radius: 3px;
13 | background-color: $color-ooni-blue;
14 | color: white;
15 |
16 | h3 {
17 | margin-top: 10px;
18 | }
19 |
20 | a {
21 | color: $color-grey;
22 | }
23 |
24 | a:hover {
25 | color: white;
26 | }
27 |
28 | .ooni-highlight-number {
29 | font-weight: 500;
30 | font-size: 2em;
31 | }
32 |
33 | .country-name {
34 | float: left;
35 | padding-right: 20px;
36 |
37 | .flag-icon {
38 | height: 50px;
39 | width: 66px;
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/client/ngapp/styles/_loading.scss:
--------------------------------------------------------------------------------
1 | /* Loading */
2 |
3 | .loading {
4 |
5 | margin-top: 50px;
6 | margin-bottom: 75px;
7 |
8 | .ooni-loader {
9 | text-align: center;
10 | }
11 |
12 | .ooni-loading-text {
13 | margin-top: 35px;
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/client/ngapp/styles/_measurement.scss:
--------------------------------------------------------------------------------
1 | /* Measurement */
2 |
3 | .anomalies {
4 | list-style: none;
5 |
6 | li {
7 | margin-left: 0;
8 | }
9 | }
10 |
11 | .measurement {
12 |
13 | h1 {
14 | margin: .5rem 0;
15 | }
16 |
17 | .measurement-info {
18 | background-color: $color-off-white;
19 | margin: 1rem 0 2rem 0;
20 | padding: 1rem 1.25rem;
21 |
22 | .detail {
23 |
24 | font-family: 'Fira Sans';
25 | color: $color-grey;
26 |
27 | &.tiny {
28 | font-size: 16px;
29 | }
30 | }
31 | }
32 |
33 | }
34 |
35 |
36 | label[for="anomalies"] {
37 | margin-top: 1rem;
38 | display: block;
39 | }
40 |
41 | .experiment-vs-control {
42 | margin-top: 1rem;
43 |
44 | table {
45 | position: relative;
46 | width: 100%;
47 | margin-top: 1rem;
48 | }
49 |
50 | thead {
51 | font-weight: bold;
52 | td {
53 | padding: 0.25rem 0.5rem
54 | }
55 | }
56 |
57 | .control-value,
58 | .experiment-value {
59 | width: 40%;
60 | }
61 |
62 | .header-name {
63 | width: 20%;
64 | }
65 |
66 | tbody td {
67 | font-size: .8rem;
68 | }
69 |
70 | .difference td {
71 | background-color: transparentize($color-blocked, 0.9);
72 | }
73 | }
74 |
75 | .CodeMirror {
76 | font-size: .8rem;
77 | background-color: $color-off-white;
78 | padding: .5rem;
79 | height: 250px;
80 | transition: height $transition-speed;
81 | }
82 |
83 | .openCodeMirror {
84 | .CodeMirror {
85 | height: 500px;
86 | }
87 | }
88 |
89 | .expandBody {
90 | color: darken($color-off-white, 20%);
91 | }
92 |
93 | .overview,
94 | .details {
95 | margin-top: 1rem;
96 |
97 | label {
98 | font-weight: bold;
99 | }
100 |
101 | .description {
102 | margin-top: 1rem;
103 | }
104 |
105 | .anomalous-result {
106 | background-color: $color-blocked;
107 | padding: 2rem;
108 | margin-bottom: 2rem;
109 | color: $color-off-white;
110 | font-size: 21px;
111 | vertical-align: middle;;
112 |
113 | i {
114 | font-size: 30px;
115 | margin-right: $base-spacing;
116 | }
117 | }
118 |
119 | .normal-result {
120 | background-color: $color-unblocked;
121 | padding: 2rem;
122 | color: $color-off-white;
123 | }
124 |
125 | .result {
126 | margin-bottom: 10px;
127 | text-align: center;
128 | }
129 |
130 | }
131 |
132 | .inline-test-spec {
133 | display: inline-block;
134 | box-sizing: border-box;
135 | margin-top: .5rem;
136 | margin-right: -5px;
137 | margin-left: 0px;
138 | border: 1px solid $color-grey;
139 | border-right: 0;
140 | padding: .5rem 1rem;
141 |
142 | &:last-child {
143 | border-right: 1px solid $color-grey;
144 | }
145 | }
146 |
147 | .note {
148 | padding: .75rem 1rem;
149 | background-color: transparentize($color-ooni-blue, .5);
150 | }
151 |
--------------------------------------------------------------------------------
/client/ngapp/styles/_mixins.scss:
--------------------------------------------------------------------------------
1 | /* Mixins */
2 |
3 | @mixin button-mixin($border) {
4 | padding: .35rem .5rem;
5 | font-size: .8rem;
6 | line-height: inherit;
7 | cursor: pointer;
8 | border: $border;
9 | font-family: "Fira Sans";
10 | font-weight: 300;
11 | transition: $transition-speed background-color, $transition-speed color;
12 | }
13 |
14 | @mixin primary-button-mixin($border, $background-color, $color) {
15 | @include button-mixin($border);
16 | background-color: $background-color;
17 | color: $color;
18 | border: $border;
19 | }
20 |
21 | @mixin placeholder-text($font-family, $font-weight) {
22 | ::-webkit-input-placeholder {
23 | font-family: $font-family;
24 | font-weight: $font-weight;
25 | }
26 |
27 | :-moz-placeholder { /* Firefox 18- */
28 | font-family: $font-family;
29 | font-weight: $font-weight;
30 | }
31 |
32 | ::-moz-placeholder { /* Firefox 19+ */
33 | font-family: $font-family;
34 | font-weight: $font-weight;
35 | }
36 |
37 | :-ms-input-placeholder {
38 | font-family: $font-family;
39 | font-weight: $font-weight;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/client/ngapp/styles/_world.scss:
--------------------------------------------------------------------------------
1 | /* World */
2 |
3 | .worldMapLegend {
4 | margin-top: -300px;
5 | margin-bottom: 50px;
6 |
7 | dl {
8 | margin-bottom: 15px;
9 | font-size: 16px;
10 |
11 | dd {
12 | display: block;
13 |
14 | span {
15 | display: inline-block;
16 | width: 12px;
17 | height: 12px;
18 | border-radius: 2px;
19 | }
20 |
21 | i {
22 | font-size: 0.6em;
23 | font-family: 'FontAwesome' !important;
24 | color: $color-red;
25 | }
26 | }
27 | }
28 | }
29 |
30 | .countries-list {
31 | list-style: none;
32 | margin: 0;
33 |
34 | li {
35 | width: 30.33%;
36 | display: inline-block;
37 | margin: 0 1.5% 2% 1.5%;
38 | padding: 0 0 1.5rem 0;
39 |
40 | border-bottom: 1px solid $color-grey-light;
41 | }
42 |
43 | .country {
44 | width: 65%;
45 | float: left;
46 | display: inline-block;
47 | font-family: "Fira Sans";
48 | font-weight: bold;
49 | font-size: 18px;
50 |
51 | i {
52 | margin-right: 10px;
53 | }
54 | }
55 |
56 | .count {
57 | float: right;
58 | background-color: $color-off-white;
59 | border-radius: $border-radius;
60 | display: inline-block;
61 | padding: .25rem .5rem;
62 | text-align: center;
63 | font-size: 14px;
64 |
65 | span {
66 | font-size: 18px;
67 | font-weight: bold;
68 | display: block;
69 | text-align: center;
70 | }
71 | }
72 |
73 | }
74 |
75 | @media (max-width: 1029px) {
76 |
77 | .countries-list {
78 | list-style: none;
79 | margin: 0;
80 |
81 | li {
82 | width: 47%;
83 | }
84 | }
85 | }
86 |
87 |
88 | @media (max-width: 809px) {
89 |
90 | .countries-list {
91 | list-style: none;
92 | margin: 0;
93 |
94 | li {
95 | width: 97%;
96 | }
97 | }
98 |
99 | .world-map-container {
100 | display: none;
101 | }
102 |
103 | }
104 |
--------------------------------------------------------------------------------
/client/ngapp/styles/fonts/charter-bold-italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/styles/fonts/charter-bold-italic.woff
--------------------------------------------------------------------------------
/client/ngapp/styles/fonts/charter-bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/styles/fonts/charter-bold.woff
--------------------------------------------------------------------------------
/client/ngapp/styles/fonts/charter-italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/styles/fonts/charter-italic.woff
--------------------------------------------------------------------------------
/client/ngapp/styles/fonts/charter-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/styles/fonts/charter-regular.woff
--------------------------------------------------------------------------------
/client/ngapp/styles/fonts/fira-sans-bold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/styles/fonts/fira-sans-bold.otf
--------------------------------------------------------------------------------
/client/ngapp/styles/fonts/fira-sans-light.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/styles/fonts/fira-sans-light.otf
--------------------------------------------------------------------------------
/client/ngapp/styles/fonts/fira-sans-semi-bold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/styles/fonts/fira-sans-semi-bold.otf
--------------------------------------------------------------------------------
/client/ngapp/styles/fonts/ooni-icons.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/styles/fonts/ooni-icons.eot
--------------------------------------------------------------------------------
/client/ngapp/styles/fonts/ooni-icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/styles/fonts/ooni-icons.ttf
--------------------------------------------------------------------------------
/client/ngapp/styles/fonts/ooni-icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/styles/fonts/ooni-icons.woff
--------------------------------------------------------------------------------
/client/ngapp/styles/fonts/source-code-pro-bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/styles/fonts/source-code-pro-bold.woff
--------------------------------------------------------------------------------
/client/ngapp/styles/fonts/source-code-pro-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/styles/fonts/source-code-pro-regular.woff
--------------------------------------------------------------------------------
/client/ngapp/styles/main.scss:
--------------------------------------------------------------------------------
1 | /* OONI Explorer - Stylesheet */
2 |
3 | // Colors
4 | $color-ooni-blue: #0588CB;
5 | $color-white: #fff;
6 | $color-off-white: #f2f2f2;
7 | $color-footer-background-color: #26292c;
8 | $color-footer-color: #b4b4b4;
9 | $color-blocked: #B83564;
10 | $color-unblocked: #4FD156;
11 | $color-red: #cc0000;
12 | $color-grey-light: #d9d9d9;
13 | $color-grey: #c1c1c1;
14 |
15 | // Misc
16 | $base-spacing: 10px;
17 | $border-radius: 3px;
18 |
19 | // Buttons
20 | $button-border: 1px solid $color-ooni-blue;
21 | $button-border-hover: 1px solid darken($color-ooni-blue, 10%);
22 | $button-border-radius: 3px;
23 |
24 | // Other
25 | $transition-speed: .5s;
26 |
27 | @import "mixins";
28 | @import "ui/fonts",
29 | "ui/table",
30 | "ui/paragraph",
31 | "ui/pagination",
32 | "ui/table-of-contents",
33 | "ui/buttons",
34 | "ui/forms",
35 | "ui/ui-grid",
36 | "ui/section",
37 | "ui/more-info-hover",
38 | "ui/icons",
39 | "base",
40 | "loading",
41 | "country",
42 | "highlights",
43 | "explore",
44 | "measurement",
45 | "world";
46 |
47 | .capitalize {
48 | text-transform: capitalize;
49 | }
50 |
51 | .header-wrapper {
52 | background-color: $color-ooni-blue;
53 |
54 | .row {
55 | margin-bottom: 1rem;
56 | margin-top: 0;
57 | }
58 |
59 | header {
60 | img {
61 | display: inline-block;
62 | vertical-align: middle;
63 | position: relative;
64 | top: -5px;
65 | left: 0px;
66 | margin-right: 20px;
67 | }
68 |
69 | h1 {
70 | display: inline-block;
71 | color: white;
72 | margin-bottom: 0;
73 | padding: 1rem 0;
74 | }
75 | }
76 |
77 | .view-chooser {
78 | text-align: center;
79 | position: relative;
80 | z-index: 999;
81 |
82 | a {
83 | @include button-mixin(3px);
84 | margin: 0 .85rem;
85 | font-size: 1rem;
86 | font-weight: bold;
87 | color: $color-white;
88 | border-radius: 3px;
89 |
90 | &:hover {
91 | background-color: darken($color-ooni-blue, 10%);
92 | color: white;
93 | }
94 |
95 | &.selected {
96 | @include primary-button-mixin(0px, $color-white, $color-ooni-blue);
97 | border-radius: 0;
98 | font-size: 1rem;
99 | font-weight: bold;
100 | line-height: inherit;
101 | border-radius: 3px;
102 |
103 | &:hover {
104 | background-color: $color-white;
105 | color: $color-ooni-blue;
106 | }
107 | }
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/client/ngapp/styles/ui-grid.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/styles/ui-grid.ttf
--------------------------------------------------------------------------------
/client/ngapp/styles/ui-grid.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ooni/explorer-legacy/19fedce6e4884376e507db6546111bf175695bed/client/ngapp/styles/ui-grid.woff
--------------------------------------------------------------------------------
/client/ngapp/styles/ui/_buttons.scss:
--------------------------------------------------------------------------------
1 | /* Buttons */
2 |
3 | button {
4 | @include primary-button-mixin($button-border, $color-ooni-blue, $color-white);
5 |
6 | &:hover {
7 | background-color: darken($color-ooni-blue, 10%);
8 | color: white;
9 | }
10 | }
11 |
12 | button.secondary {
13 | background-color: $color-white;
14 | color: $color-ooni-blue;
15 |
16 | &:hover {
17 | background-color: darken($color-ooni-blue, 10%);
18 | color: white;
19 | }
20 | }
21 |
22 | button.secondary.selected {
23 | background-color: darken($color-ooni-blue, 10%);
24 | color: $color-white;
25 | }
26 |
--------------------------------------------------------------------------------
/client/ngapp/styles/ui/_fonts.scss:
--------------------------------------------------------------------------------
1 | /* Fonts */
2 |
3 | @font-face {
4 | font-family: "Charter";
5 | src: url("fonts/charter-regular.woff");
6 | }
7 |
8 | @font-face {
9 | font-family: "Charter";
10 | src: url("fonts/charter-bold.woff");
11 | font-weight: bold;
12 | }
13 |
14 | @font-face {
15 | font-family: "Charter";
16 | src: url("fonts/charter-italic.woff");
17 | font-style: italic;
18 | }
19 |
20 | @font-face {
21 | font-family: "Charter";
22 | src: url("fonts/charter-bold-italic.woff");
23 | font-weight: bold; font-style: italic;
24 | }
25 |
26 | @font-face {
27 | font-family: "Fira Sans";
28 | src: url("fonts/fira-sans-bold.otf");
29 | font-weight: bold;
30 | }
31 |
32 | @font-face {
33 | font-family: "Fira Sans";
34 | src: url("fonts/fira-sans-semi-bold.otf");
35 | font-weight: 500;
36 | }
37 |
38 | @font-face {
39 | font-family: "Fira Sans";
40 | src: url("fonts/fira-sans-light.otf");
41 | font-weight: 300;
42 | }
43 |
44 | @font-face {
45 | font-family: "Source Code Pro";
46 | src: url("fonts/source-code-pro-regular.woff");
47 | }
48 |
49 | @font-face {
50 | font-family: "Source Code Pro";
51 | src: url("fonts/source-code-pro-bold.woff");
52 | font-weight: bold;
53 | }
54 |
--------------------------------------------------------------------------------
/client/ngapp/styles/ui/_forms.scss:
--------------------------------------------------------------------------------
1 | /* Forms */
2 |
3 | .fa-chevron-circle-down {
4 | transition: $transition-speed;
5 | }
6 |
7 | .fa-chevron-circle-down.open {
8 | transform: rotate(180deg);
9 | }
10 |
11 | .daterangepicker.dropdown-menu {
12 | display: none;
13 | }
14 |
15 | // Specific Forms
16 |
17 | .filter-toggle {
18 | margin: 1rem 0 1rem;
19 | cursor: pointer;
20 | display: block;
21 | }
22 |
23 | .filter-form {
24 | @include placeholder-text("Fira Sans", 300)
25 |
26 | padding: 1.5rem 1.5rem 0 1.5rem;
27 | background: $color-off-white none repeat scroll 0% 0%;
28 | margin: 0 0 1.5rem 0;
29 |
30 | .form-group {
31 | margin: 0 1.5rem 1.5rem 0;
32 | display: block;
33 | }
34 |
35 | .form-group.inline {
36 | display: inline-block;
37 | }
38 |
39 | select,
40 | input {
41 | font-size: .8rem;
42 | padding: .25rem .5rem;
43 | }
44 |
45 | input {
46 | border-radius: $button-border-radius;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/client/ngapp/styles/ui/_icons.scss:
--------------------------------------------------------------------------------
1 | /* Icons */
2 |
3 | @font-face {
4 | font-family: 'ooni-icons';
5 | src: url('fonts/ooni-icons.eot?lb0vhf');
6 | src: url('fonts/ooni-icons.eot?lb0vhf#iefix') format('embedded-opentype'),
7 | url('fonts/ooni-icons.ttf?lb0vhf') format('truetype'),
8 | url('fonts/ooni-icons.woff?lb0vhf') format('woff'),
9 | url('fonts/ooni-icons.svg?lb0vhf#ooni-icons') format('svg');
10 | font-weight: normal;
11 | font-style: normal;
12 | }
13 |
14 | @mixin ooni-icons {
15 | /* use !important to prevent issues with browser extensions that change fonts */
16 | font-family: 'ooni-icons' !important;
17 | speak: none;
18 | font-style: normal;
19 | font-weight: normal;
20 | font-variant: normal;
21 | text-transform: none;
22 | line-height: 1;
23 |
24 | /* Better Font Rendering =========== */
25 | -webkit-font-smoothing: antialiased;
26 | -moz-osx-font-smoothing: grayscale;
27 | }
28 |
29 |
30 | .icon-analysis {
31 | @include ooni-icons;
32 | }
33 | .icon-analysis:before {
34 | content: "\e900";
35 | }
36 | .icon-censorhip-tampering {
37 | @include ooni-icons;
38 | }
39 | .icon-censorhip-tampering:before {
40 | content: "\e901";
41 | }
42 | .icon-censorhip-vendor {
43 | @include ooni-icons;
44 | }
45 | .icon-censorhip-vendor:before {
46 | content: "\e902";
47 | }
48 | .icon-censorship-confirmed {
49 | @include ooni-icons;
50 | }
51 | .icon-censorship-confirmed:before {
52 | content: "\e903";
53 | }
54 | .icon-country-statistics {
55 | @include ooni-icons;
56 | }
57 | .icon-country-statistics:before {
58 | content: "\e904";
59 | }
60 | .icon-decentralization {
61 | @include ooni-icons;
62 | }
63 | .icon-decentralization:before {
64 | content: "\e905";
65 | }
66 | .icon-distributed {
67 | @include ooni-icons;
68 | }
69 | .icon-distributed:before {
70 | content: "\e906";
71 | }
72 | .icon-http-body {
73 | @include ooni-icons;
74 | }
75 | .icon-http-body:before {
76 | content: "\e907";
77 | }
78 | .icon-http-headers {
79 | @include ooni-icons;
80 | }
81 | .icon-http-headers:before {
82 | content: "\e908";
83 | }
84 | .icon-measurement {
85 | @include ooni-icons;
86 | }
87 | .icon-measurement:before {
88 | content: "\e909";
89 | }
90 | .icon-progress {
91 | @include ooni-icons;
92 | }
93 | .icon-progress:before {
94 | content: "\e90a";
95 | }
96 | .icon-research {
97 | @include ooni-icons;
98 | }
99 | .icon-research:before {
100 | content: "\e90b";
101 | }
102 | .icon-server {
103 | @include ooni-icons;
104 | }
105 | .icon-server:before {
106 | content: "\e90c";
107 | }
108 | .icon-tor {
109 | @include ooni-icons;
110 | }
111 | .icon-tor:before {
112 | content: "\e90d";
113 | }
114 |
--------------------------------------------------------------------------------
/client/ngapp/styles/ui/_more-info-hover.scss:
--------------------------------------------------------------------------------
1 | /* More Info Hover */
2 |
3 | .hover-wrapper {
4 | position: relative;
5 | cursor: pointer;
6 |
7 | .hover-definition {
8 | display: none;
9 | position: absolute;
10 | background: rgba(0, 0, 0, .8);
11 | color: white;
12 | padding: .5rem 1rem;
13 | width: 20rem;
14 | opacity: 0;
15 | top: 2rem;
16 | left: 0;
17 | vertical-align: top;
18 | }
19 |
20 | &:hover .hover-definition {
21 | display: inline-block;
22 | opacity: 1;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/client/ngapp/styles/ui/_pagination.scss:
--------------------------------------------------------------------------------
1 | /* Pagination */
2 |
3 | .pagination {
4 | margin: 0 1rem 1rem 0;
5 | display: inline-block;
6 |
7 | li {
8 | display: inline-block;
9 | margin: 0;
10 | padding: .25rem .5rem;
11 | border: 1px solid $color-off-white;
12 | border-right: 0px;
13 | }
14 |
15 | a {
16 | cursor: pointer;
17 | }
18 |
19 | li:last-child {
20 | border-top-right-radius: 3px;
21 | border-bottom-right-radius: 3px;
22 | border-right: 1px solid $color-off-white;
23 | }
24 | li:first-child {
25 | border-top-left-radius: 3px;
26 | border-bottom-left-radius: 3px;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/client/ngapp/styles/ui/_paragraph.scss:
--------------------------------------------------------------------------------
1 | /* Paragraph */
2 |
3 | .highlight {
4 | background-color: lighten($color-ooni-blue, 50%);
5 | padding-left: .5rem;
6 | padding-right: .5rem;
7 | }
8 |
--------------------------------------------------------------------------------
/client/ngapp/styles/ui/_section.scss:
--------------------------------------------------------------------------------
1 | /* Section */
2 |
3 | .country section,
4 | .website section {
5 | margin-top: 2em;
6 |
7 | h2 {
8 | margin-bottom: 1em;
9 | }
10 |
11 | .no-show {
12 | background-color: $color-off-white;
13 | padding: 2rem;
14 | text-align: center;
15 | color: $color-grey;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/client/ngapp/styles/ui/_table-of-contents.scss:
--------------------------------------------------------------------------------
1 | /* Table of Contents */
2 |
3 | .toc {
4 |
5 | border: 1px solid $color-grey;
6 | padding: .5rem 1rem;
7 | display: inline-block;
8 | list-style: none;
9 | vertical-align: top;
10 | margin-right: .5rem;
11 | font-size: 14px;
12 |
13 | li {
14 | padding: 0;
15 | margin: 0;
16 | }
17 |
18 | a {
19 | cursor: pointer;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/client/ngapp/styles/ui/_table.scss:
--------------------------------------------------------------------------------
1 | /* Table */
2 |
3 | table {
4 | width: 100%;
5 | border-collapse: collapse;
6 | }
7 |
8 | thead {
9 | th {
10 | font-weight: bold;
11 | text-align: left;
12 | }
13 | }
14 |
15 | tbody {
16 | td,
17 | th {
18 | padding: .25rem .5rem;
19 | transition: $transition-speed background-color;
20 | }
21 |
22 | th {
23 | font-weight: 600;
24 | text-align: left;
25 | }
26 |
27 | tr:nth-child(even) td,
28 | tr:nth-child(even) th{
29 | background-color: darken($color-white, 2%);
30 | }
31 |
32 | tr:hover td,
33 | tr:hover th {
34 | background-color: $color-off-white;
35 | }
36 | }
37 |
38 | table.countries {
39 | td {
40 | cursor: pointer;
41 | }
42 | }
43 |
44 | .clickable {
45 | cursor: pointer;
46 |
47 | i::before {
48 | color: $color-off-white;
49 | transition: color $transition-speed;
50 | }
51 |
52 | &:hover {
53 | i::before {
54 | color: $color-ooni-blue;
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/client/ngapp/styles/ui/_ui-grid.scss:
--------------------------------------------------------------------------------
1 | /* UI Grid */
2 |
3 | .ui-grid {
4 | border: none;
5 | }
6 |
7 | .ui-grid-header {
8 | border-top: 3px solid $color-ooni-blue;
9 |
10 | .ui-grid-top-panel {
11 | background: transparent;
12 | }
13 |
14 | .ui-grid-header-cell {
15 | border-right: none;
16 | }
17 | }
18 |
19 | .ui-grid-row {
20 | cursor: pointer;
21 |
22 | &:hover {
23 | .ui-grid-cell {
24 | background-color: darken($color-white, 10%);
25 | }
26 | }
27 | }
28 |
29 | .ui-grid-viewport {
30 | .ui-grid-cell {
31 | border-right: none;
32 | }
33 | }
34 |
35 | .row-link {
36 | color: #000;
37 | }
38 |
--------------------------------------------------------------------------------
/client/ngapp/views/about.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
What is OONI?
4 |
5 |
The Open Observatory of Network Interference (OONI) is a free software project under the Tor Project which collects and processes network measurements with the aim of detecting network anomalies, such as censorship, surveillance and traffic manipulation. Since late 2012, OONI has collected millions of measurements across more than 90 countries around the world.
6 |
7 |
What is OONI Explorer?
8 |
9 |
OONI Explorer is a global map which provides a location to explore and interact with all of the network measurements that have been collected through OONI tests from 2012 until today. These tests are designed to:
10 |
11 | Detect the blocking of websites
12 | Detect systems responsible for censorship, surveillance and traffic manipulation
13 | Evaluate the reachability of Tor bridges , proxies, VPNs, and sensitive domains
14 |
15 |
16 |
17 |
18 |
OONI Explorer serves as a resource for researchers, journalists, lawyers, activists, advocates and anyone interested in exploring network anomalies, such as censorship, surveillance and traffic manipulation, and how the internet works in general.
19 |
20 |
Note: The network measurements included in OONI Explorer were collected in specific moments in time and within specific networks and do not necessarily reflect the overall levels of censorship and traffic manipulation across time or on country-wide levels.
21 |
22 |
Contact
23 |
24 | To contact the OONI team send an email to contact [AT] openobservatory.org .
25 | Encrypted emails can be sent using the following PGP key:
26 |
pub 4096R/6B2943F00CB177B7 2016-03-23 [expires: 2018-03-26]
27 | Key fingerprint = 4C15 DDA9 96C6 C0CF 48BD 3309 6B29 43F0 0CB1 77B7
28 | uid [ultimate] OONI - Open Observatory of Network Interference
29 | sub 4096R/8EBD2087374399AB 2016-03-23 [expires: 2018-03-26]
30 |
31 |
Credits
32 |
33 |
OONI Explorer contributors:
34 |
35 |
36 | Arturo Filastò
37 | Brennan Novak
38 | Simon Vansintjan
39 | Joe Landers
40 | Vasilis Ververis
41 | Tyler Fisher
42 | Will Scott
43 | Guy Hughes
44 | Maria Xynou
45 |
46 |
47 |
48 |
49 |
50 |
Special thanks to:
51 |
52 |
53 | the German Federal Foreign Office for funding OONI Explorer
54 | all the volunteers around the world who have contributed to OONI over the years
55 | the Open Technology Fund (OTF) for funding OONI since its start
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/client/ngapp/views/country-view.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
30 |
31 |
32 |
33 |
34 | Here's what we know about anomalies with internet connections located in {{ countryName | lowercase }} .
35 |
36 |
37 |
{{ count | number }} Measurements Collected
38 |
39 |
40 |
43 |
44 |
45 |
47 |
48 |
49 |
52 |
53 |
54 |
55 |
56 | Country Internet Statistics
57 |
58 |
Datasource: World Fact Book
59 |
60 |
61 |
62 |
66 |
67 |
70 |
71 |
74 |
75 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | Methods of Censorship
85 |
91 |
92 |
93 |
94 | Identified Vendors
95 |
96 |
97 | {{vendor.vendor}}
98 | ASN: {{ vendor.probe_asn }}
99 | Identified on: {{ vendor.test_start_time | date:'shortDate'}}
100 | view measurement »
101 |
102 |
103 |
104 | We've identified no vendors for this country.
105 |
106 |
107 |
108 |
109 | Blocked Websites
110 |
111 |
112 | {{ obj.input }} :
113 |
117 |
118 |
121 |
122 |
123 | {{ measurement.test_start_time | date:'shortDate' }}
124 | view »
125 |
126 |
127 |
128 |
130 | loading more...
131 |
132 |
133 | We've identified no blocked pages for this country.
134 |
135 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/client/ngapp/views/directives/ooni-country-bar-chart.directive.html:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 | The graph above shows when we've run tests, and how many sites we've found to be blocked out of all measured .
20 |
21 |
--------------------------------------------------------------------------------
/client/ngapp/views/directives/ooni-explorer-list-measurement.directive.html:
--------------------------------------------------------------------------------
1 |
4 |
12 |
21 | {{ measurement.probe_asn }}
22 | {{ measurement.test_start_time|date:'shortDate' }}
23 |
--------------------------------------------------------------------------------
/client/ngapp/views/directives/ooni-filter-list-form.directive.html:
--------------------------------------------------------------------------------
1 |
2 | Filter Results
3 |
4 |
5 |
6 |
41 |
--------------------------------------------------------------------------------
/client/ngapp/views/directives/ooni-grid-wrapper-directive.html:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/client/ngapp/views/directives/ooni-info-country-list.directive.html:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
--------------------------------------------------------------------------------
/client/ngapp/views/directives/ooni-info-explorer-list.directive.html:
--------------------------------------------------------------------------------
1 |
6 |
7 |
12 |
13 |
14 |
15 |
21 |
22 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/client/ngapp/views/directives/ooni-loader.directive.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
--------------------------------------------------------------------------------
/client/ngapp/views/directives/ooni-more-info-hover-directive.html:
--------------------------------------------------------------------------------
1 |
2 | {{ label }}: {{ content }}
3 | {{ definition.description }}
4 |
5 |
--------------------------------------------------------------------------------
/client/ngapp/views/directives/ooni-pagination.directive.html:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 | {{ total|number }} measurements
13 |
14 |
--------------------------------------------------------------------------------
/client/ngapp/views/directives/ooni-report-detail-table-row.html:
--------------------------------------------------------------------------------
1 |
2 | {{ label }}
3 |
4 |
5 | {{ content }}
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/client/ngapp/views/directives/row-template.html:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/client/ngapp/views/explore.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Explore our measurements from various types of tests and countries. Use the filter to narrow down what you're looking for.
4 |
5 |
7 |
8 |
13 |
14 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/client/ngapp/views/highlights.html:
--------------------------------------------------------------------------------
1 | Highlights
2 |
3 | Below is a selection of highlights which illustrate the presence of internet censorship and/or traffic manipulation within certain countries.
4 |
5 |
6 |
7 |
10.8 million network measurements have been collected across 96 countries around the world in 3 years (from late 2012 to early 2016).
8 |
9 |
10 |
11 |
Network anomalies have been detected in 71 out of 91 tested countries.
12 |
However, not all network anomalies are necessarily cases of censorship and/or traffic manipulation.
13 |
14 |
15 |
16 |
17 |
18 |
19 |
12 countries have confirmed cases of censorship : Russia , China , Iran , Saudi Arabia , Turkey , India , Indonesia , Greece , Sudan , Belgium , Cyprus and Korea .
20 |
By "confirmed censorship" we mean that we have an accurate heuristic to determine when blocking is occurring. Generally this means that we are looking for a block page in the response body of a HTTP request test.
21 |
22 |
23 |
24 |
3 vendors have been identified through our network measurements: Blue Coat, Squid and Privoxy . The use of their software has been detected in 12 countries around the world: USA , Canada , Portugal , Spain , Italy , the Netherlands , Switzerland , Moldova , Iraq , Uganda , Myanmar and Great Britain .
25 |
Such software may or may not have been responsible for online censorship and/or traffic manipulation.
26 |
27 |
28 |
29 |
50 |
51 |
52 |
53 |
54 |
Uganda
55 |
56 |
Following reports that Facebook and Twitter were blocked in Uganda leading up to its 2016 general elections, OONI tests were run in the country. While we were unable to detect block pages, the following are possibilities:
57 |
58 | Facebook and Twitter were only blocked in specific networks (excluding the one we tested), and not countrywide.
59 | Ugandan ISPs did not forward their users' requests to connect to the servers of Facebook and Twitter.
60 | Ugandan ISPs chose to block some requests, while not others. Over the last three years of conducting network measurments, we have noticed that this often appears to be a common practice of censorship by ISPs around the world.
61 |
62 |
63 |
go to country page »
64 |
65 |
66 |
67 |
68 |
69 |
Myanmar
70 |
71 |
Blue Coat software (some types of which can potenially be used for internet filtering, censorship and surveillance) was detected in Myanmar through one of our (HTTP-field-manipulation) tests in late 2012. It's unclear, however, if this was a countrywide deployment or if it was only used in the specific network that we tested.
72 |
Two years later we ran the same test in the same network in Myanmar, but did not detect the presence of Blue Coat.
73 |
go to country page »
74 |
75 |
76 |
77 |
78 |
86 |
87 |
88 |
89 |
90 |
India
91 |
92 |
In January 2015 the Government of India ordered Internet Service Licensees to block 32 websites under Section 69A of the Information Technology Act, 2000, and under the Information Technology (Procedures and Safeguards for Blocking of Access of Information by Public) Rules, 2009.
93 |
94 |
We ran network measurements on those 32 websites and detected block pages (confirmed cases of censorship) for 23 of them, including dailymotion.com , pastebin.com and archive.org .
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
Pakistan
103 |
104 |
Multiple (HTTP-invalid-request-line) tests conducted in Pakistan show that "middle boxes" (software which could potentially be used for censorship and/or traffic manipulation) were present in Pakistani networks. However, it remains unclear if such software was actually used for the purpose of censorship and/or traffic manipulation.
105 |
go to country page »
106 |
107 |
108 |
109 |
110 |
111 |
Vietnam
112 |
113 |
Multiple (HTTP-invalid-request-line) tests conducted in Vietnam from 2013 to 2015 show that "middle boxes" (software which could potentially be used for censorship and/or traffic manipulation) were present in Vietnamese networks. However, it remains unclear if such software was actually used for the purpose of censorship and/or traffic manipulation.
114 |
go to country page »
115 |
116 |
117 |
--------------------------------------------------------------------------------
/client/ngapp/views/nettests/bridge-reachability.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | body_length_match: {{ report.test_keys.body_length_match }}
5 | input: {{ report.input }}
6 |
7 |
8 |
9 |
Tor Bridge Reachability
10 |
11 |
This test examines whether Tor bridges work in tested networks.
12 |
13 |
Tor is free and open
14 | source software which enables online anonymity and censorship
15 | circumvention. It was designed to bounce communications around a
16 | distributed network of relays run by volunteers around the world,
17 | thus hiding users' IP address and circumventing online tracking and
18 | censorship. However, Internet Service Providers (ISPs) in various
19 | countries around the world are often ordered by their governments
20 | to block users' access to Tor. As a result,
21 | Tor bridges were developed to enable users to
22 | connect to the Tor network in countries where such access is blocked.
23 |
24 |
25 |
This test runs Tor with a list of bridges and if it's able to connect to
26 | them successfully, we consider that Tor bridges are not blocked in the tested
27 | network. If the test, however, is unable to bootstrap a connection, then the
28 | Tor bridges are either offline or blocked.
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/client/ngapp/views/nettests/bridget.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | body_length_match: {{ report.test_keys.body_length_match }}
5 | input: {{ report.input }}
6 |
7 |
8 |
9 |
Tor Bridge
10 |
Detect whether or not a Tor bridge is reachable from a specific network vantage point on a network.
11 |
TODO: This description needs to be made better.
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/client/ngapp/views/nettests/captive-portal.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Details for this specific test:
5 |
11 |
12 |
13 |
14 |
Captive Portal
15 |
16 | This test emulates the requests that common software vendors use to
17 | detect the presence of a captive portal. A captive portal is a
18 | device between the users and the internet, generally used to
19 | implement authentication to the network.
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/client/ngapp/views/nettests/dns-consistency.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Inconsistent resolvers: {{ report.test_keys.inconsistent }}
5 |
6 |
Website: {{ report.input }}
7 |
View Website Information »
8 |
9 |
10 |
11 |
DNS Consistency
12 |
This test compares the DNS query results from a DNS resolver which is considered to be reliable with one that is tested for tampering.
13 |
14 | The domain name system (DNS) is what is responsible for transforming a host name (e.g. torproject.org) into an IP address (e.g. 38.229.72.16). ISPs, amongst others, run DNS resolvers which map IP addresses to host names. In certain circumstances though, ISPs map the wrong IP addresses to the wrong host names. This is a form of tampering, which OONI can detect by running its DNS consistency test.
15 |
This test compares the IP address of a given host name allocated by the Google DNS resolver (which we assume to not be tampered with) with the IP address mapped to that website by a provider. If the two IP addresses of the same website are different, then there is a sign of network interference. When ISPs tamper with DNS answers, users are redirected to other websites or fail to connect to their intended websites.
16 |
17 | Note: DNS resolvers, such as Google or your local ISP, often provide users with IP addresses that are closest to them geographically. Often this is not done with the intent of network tampering, but merely for the purpose of providing users faster access to websites. As a result, some false positives might arise in OONI measurements.
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/client/ngapp/views/nettests/dns-injection.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
DNS Injection
5 |
6 | Ability to detect the presence of DNS Injection and list of domains
7 | that are being blocked via DNS injection.
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/client/ngapp/views/nettests/dns-spoof.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | body_length_match: {{ report.test_keys.body_length_match }}
5 | input: {{ report.input }}
6 |
7 |
8 |
9 |
DNS Spoof
10 |
This test assesses if the blocking is happening due to DNS
11 | spoofing or not. DNS spoofing occurs when your connection to a DNS
12 | resolver is spoofed and you always receive a falsified answer. For
13 | example, you know that a certain domain is blocked on a given network.
14 | So you take this domain, the DNS resolver of the network and
15 | then that of a DNS resolver which you know that doesn't lie
16 | (e.g. Google DNS resolver). If they both match, that means that
17 | something between you and Google has spoofed the response and thus
18 | DNS censorship is happening by spoofing the responses.
19 |
20 | Note: If you run this test with a site that is not censored, it
21 | will tell you that spoofing is occurring (because they match),
22 | while it's actually not. In such cases, you may have false
23 | positives.
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/client/ngapp/views/nettests/http-header-field-manipulation.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Header field name tampering:
5 |
6 | Header field value tampering:
7 |
8 | Header field number tampering:
9 |
10 | Header name capitalization:
11 |
12 | Request line capitalization tampering:
13 |
14 | Total tampering: {{ report.test_keys.tampering.total }}
15 |
16 | Header name diff: {{ report.test_keys.tampering.header_name_diff }}
17 |
18 |
19 |
20 |
HTTP Header Manipulation
21 |
22 |
This test tries to detect the presence of censorship and/or
23 | surveillance software (“middle box”) which could be responsible for
24 | traffic manipulation.
25 |
26 |
HTTP is a protocol which transfers or exchanges data across the internet. It
27 | does so by handling a client's request to connect to a server, and a server's
28 | response to a client's request. Every time you connect to a server, you (the
29 | client) send a request through the HTTP protocol to that server. Such requests
30 | include “HTTP headers”, which transmit various types of information, including
31 | your device's operating system and the type of browser that it's using. If you
32 | are using Firefox on Windows, for example, the “user agent header” in your HTTP
33 | request will tell the server that you're trying to connect to that you're using
34 | a Firefox browser on a Windows operating system.
35 |
36 |
This test emulates what would have been a valid HTTP request towards a
37 | server, but instead sends HTTP headers that have variations in capitalization.
38 | In other words, this test sends HTTP requests which include valid, but non-standard
39 | HTTP headers. Such requests are sent to a backend control server which sends back
40 | any data it receives. If we receive the HTTP headers exactly as we sent them,
41 | then we assume that there is no “middle box” in the network which could be
42 | responsible for censorship, surveillance and/or traffic manipulation. If,
43 | however, such software is present in the network that we are testing, it will
44 | likely normalize the invalid headers that we are sending or add extra headers.
45 |
46 |
Depending on whether the HTTP headers that we send and receive from a
47 | backend control server are the same or not, we are able to evaluate whether
48 | software – which could be responsible for traffic manipulation – is present in
49 | the network that we are testing.
50 |
51 |
Note: A false negative could potentially occur in the
52 | hypothetical instance that ISPs are using highly sophisticated software
53 | that is specifically designed to not interfere with invalid HTTP headers
54 | when it receives them. Furthermore, the presence of a middle box is not
55 | necessarily indicative of traffic manipulation, as they are often used in
56 | networks for caching purposes.
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/client/ngapp/views/nettests/http-host.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Send host header:
5 |
6 | Filtering of subdomain: {{ report.test_keys.filtering_of_subdomain }}
7 |
8 | Transparent HTTP proxy: {{ report.test_keys.transparent_http_proxy }}
9 |
10 | Filtering by adding tab to host: {{ report.test_keys.filtering_add_tab_to_host }}
11 |
12 | Filtering via fuzzy matching: {{ report.test_keys.filtering_via_fuzzy_matching }}
13 |
14 | Filtering by prepending newline to method: {{ report.test_keys.filtering_prepend_newline_to_method }}
15 |
16 | Website: {{ report.input }}
17 |
18 |
19 |
20 |
HTTP Host
21 |
This test attempts to:
22 |
23 |
24 | examine whether the domain names of websites are blocked
25 | detect the presence of “middle boxes” (software which could be used for censorship and/or traffic manipulation) in tested networks
26 | assess which censorship circumvention techniques are capable of bypassing the censorship implemented by the “middle box”
27 |
28 |
29 |
HTTP is a protocol which transfers or exchanges data across the internet. It
30 | does so by handling a client's request to connect to a server, and a server's
31 | response to a client's request. Every time you connect to a server, you (the
32 | client) send a request through the HTTP protocol to that server. Such requests
33 | include “HTTP headers”, some of which (the “Host header”) include information
34 | about the specific domain that you want to connect to. When you connect to
35 | torproject.org, for example, the host header of your HTTP request includes
36 | information which communicates that you want to connect to that domain.
37 |
38 |
This test implements a series of techniques which help it evade getting
39 | detected from censors and then uses a list of domain names (such as bbc.co.uk)
40 | to connect to an OONI backend control server, which sends a JSON structure
41 | containing the host headers of those domain names back to us. If a “middle box”
42 | is detected between the network path of the probe and the OONI backend control
43 | server, its fingerprint might be included in the JSON data that we receive from
44 | the backend control server. Such data also informs us if the tested domain
45 | names are blocked or not, as well as how the censor tried to fingerprint the
46 | censorship of those domains. This can sometimes lead to the identification of
47 | the type of infrastructure being used to implement censorship.
48 |
49 |
Note: The presence of a middle box is not necessarily
50 | indicative of censorship and/or traffic manipulation, as they are often used in
51 | networks for caching purposes.
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/client/ngapp/views/nettests/http-invalid-request-line.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Details for this specific test:
5 |
6 |
15 |
16 |
17 |
18 |
HTTP Invalid Request
19 |
This test tries to detect the presence of censorship and/or surveillance software (“middle box”) which could be responsible for traffic manipulation.
20 |
21 |
A very useful debugging and measurement tool is an echo service, which simply sends back to the originating source any data it receives. Instead of sending a normal HTTP request, this test sends an invalid HTTP request line - containing an invalid HTTP version number, an invalid field count and a huge request method – to an echo service listening on the standard HTTP port. If a middle box is not present in the network between the user and an echo service, then the echo service will send the invalid HTTP request line back to the user, exactly as it received it. In such cases, we assume that there is no visible traffic manipulation in the tested network.
22 |
23 |
If, however, a middle box is present in the tested network, the invalid HTTP request line will be intercepted by the middle box and this may trigger an error and that will subsequently be sent back to OONI. Such errors indicate that software for traffic manipulation is likely placed in the tested network, though it's not always clear what that software is. In some cases though, we are able to identify censorship and/or surveillance vendors through the error messages in the received invalid HTTP responses.
24 |
25 |
So far, we have detected, thanks to this technique, the use of BlueCoat, Squid and Privoxy in networks across 11 countries around the world.
26 |
27 |
Note: A false negative could potentially occur in the hypothetical instance that ISPs are using highly sophisticated censorship and/or surveillance software that is specifically designed to not trigger errors when receiving invalid HTTP request lines like the ones of this test. Furthermore, the presence of a middle box is not necessarily indicative of traffic manipulation, as they are often used in networks for caching purposes.
28 |
29 |
30 |
31 |
32 |
Content of responses from middlebox
33 |
34 |
35 |
Response number {{$index}}
36 |
39 |
40 |
41 |
42 |
Response number {{$index}} was empty
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/client/ngapp/views/nettests/http-requests.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | This measurement contains data that could be a sign of network tampering or censorship.
6 |
7 |
8 | This measurement looks normal.
9 |
10 |
11 |
23 |
24 |
25 |
26 |
HTTP Requests
27 |
This test tries to detect online censorship based on a comparison of HTTP requests over Tor and over the network of the user.
28 |
29 |
HTTP is a protocol which allows communication between a client and a server. It does so by handling a client's request to connect to a server, and a server's response to a client's request. Every time you connect to a website, your browser (the client) sends a request through the HTTP protocol to the server which is hosting that website. A server normally responds with the content of the website it is hosting. In some cases though, Internet Service Providers (ISP) prevent users from accessing certain websites by blocking or interfering with the connection between them and the server.
30 |
31 |
To detect such cases of censorship, we have developed a test which performs HTTP requests to given websites over the network of its user, and then over the Tor network. As Tor software is designed to circumvent censorship by making its user's traffic appear to come from a different part of the world, we have chosen to use the Tor network as a baseline for comparing HTTP requests to websites. If the two results match, then there is no clear sign of network interference; but if the results are different, then the website that the user is testing is likely censored.
32 |
33 |
If one of the following is present in the results, then there is a sign of network interference:
34 |
35 | The length of the body of the two websites (over Tor and over the user's network) differs by some percentage
36 | The HTTP request over the user's network fails
37 | The HTTP headers do not match
38 |
39 |
40 |
41 | Note: False positives might occur when the Tor control connection is being discriminated by the server. This happens, for example, when a CloudFlare CAPTCHA page appears.
42 |
43 |
44 |
45 |
46 |
47 |
HTTP Response Headers
48 |
49 | View:
All Headers
50 |
Different Headers
51 |
52 |
53 |
54 | Control
55 | Experiment
56 |
57 |
58 |
61 |
64 | {{ control.response.headers[header_name] }}
65 | {{ experiment.response.headers[header_name] }}
66 |
67 |
68 |
69 |
70 |
71 |
HTTP Response Body
72 |
76 |
77 |
expand ›
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/client/ngapp/views/nettests/lantern-circumvention-tool-test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Successfully connected: {{ report.test_keys.success }}
5 |
6 |
7 |
8 |
Lantern Circumvention Tool
9 |
10 |
This test provides an automated way of examining whether Lantern works in a tested network.
11 |
12 |
Lantern is a centralized and
13 | peer-to-peer proxy, which is used as a circumvention tool. It detects whether
14 | websites are blocked and, if so, it allows you to access them via Lantern
15 | servers or via the network of Lantern users.
16 |
17 |
This test runs Lantern and checks to see if it is working. If it's
18 | able to connect to a Lantern server and reach a control website over it,
19 | then we consider that Lantern can be used for censorship circumvention
20 | within the tested network. If however the test is unable to connect
21 | to Lantern servers, then it is likely the case that they are blocked
22 | within the tested network.
23 |
24 |
25 |
26 |
27 |
Console standard output
28 |
31 |
32 |
33 |
Console error output
34 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/client/ngapp/views/nettests/meek-fronted-requests-test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Successfully connected: {{ report.test_keys.success }}
5 |
6 |
7 |
8 |
Meek Fronted Requests
9 |
10 |
This test examines whether the domains used by Meek (a type of Tor bridge) work in tested networks.
11 |
12 |
Meek is a pluggable transport which uses non-blocked domains, such as
13 | google.com , awsstatic.com (Amazon cloud infrastructure) and
14 | ajax.aspnetcdn.com (Microsoft azure cloud infrastructure), to proxy its users over
15 | Tor to blocked websites, while hiding
16 | both the fact that they are connecting to such websites and how they are
17 | connecting to them. As such, Meek is useful for not only connecting to websites
18 | that are blocked, but for also hiding which websites you are connecting to.
19 |
20 |
Below is a simplified explanation of how this works:
21 |
22 |
[user] → [https://www.google.com] → [Meek hosted on the cloud] → [Tor] → [blocked-website]
23 |
24 |
The user will receive a response (access to a blocked website,
25 | for example) from cloud-fronted domains, such as google.com,
26 | through the following way:
27 |
28 |
[blocked-website] → [Tor] → [Meek hosted on the cloud] → [https://www.google.com] → [user]
29 |
30 |
In short, this test does an encrypted connection to
31 | cloud-fronted domains over HTTPS and examines whether it can
32 | connect to them or not.
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/client/ngapp/views/nettests/multi-protocol-traceroute.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | body_length_match: {{ report.test_keys.body_length_match }}
5 | input: {{ report.input }}
6 |
7 |
8 |
9 |
Multi-Protocol Traceroute
10 |
11 | Every time we connect to a server, our devices make requests which are encapsulated in a packet. A traceroute is a way of understanding which paths packets take in a network. OONI constructs packets in such a way that they perform a traceroute from multiple protocols and ports simultaneously, in an attempt to detect traffic manipulation.
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/client/ngapp/views/nettests/nettest.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/client/ngapp/views/nettests/openvpn.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | body_length_match: {{ report.test_keys.body_length_match }}
5 | input: {{ report.input }}
6 |
7 |
8 |
9 |
OpenVPN
10 |
11 |
This test provides an automated way of examining whether OpenVPN works in a tested network.
12 |
13 |
OpenVPN is an open source
14 | application VPN (Virtual Private Network) protocol that allows a user to send and receive
15 | network data as if they were connected directly to another private network.
16 | As such it is commonly used for censorship circumvention purposes.
17 |
18 |
19 |
This test runs OpenVPN and checks to see if it is working. If it's able to
20 | connect to an OpenVPN server, then we consider that OpenVPN can be used for
21 | censorship circumvention within the tested network. If however the test is
22 | unable to connect to OpenVPN servers, then it is likely the case that they are
23 | blocked within the tested network.
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/client/ngapp/views/nettests/psiphon-test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Request success: {{ report.test_keys.request_success }}
5 |
6 | Bootstrapped success: {{ report.test_keys.bootstrapped_success }}
7 |
8 |
9 |
10 |
Psiphon
11 |
12 |
This test provides an automated way of examining whether Psiphon works in a tested network.
13 |
14 |
Psiphon is a free and open
15 | source tool that utilises SSH, VPN and HTTP proxy technology for
16 | censorship circumvention.
17 |
18 |
This test runs Psiphon and checks to see if it is working. If
19 | it's able to connect to a Psiphon server and reach a website over it,
20 | then we consider that Psiphon can be used for censorship
21 | circumvention within the tested network. If however the test is
22 | unable to connect to Psiphon servers, then it is likely the case
23 | that they are blocked within the tested network.
24 |
25 |
26 |
27 |
28 |
Console standard output
29 |
32 |
33 |
34 |
Console error output
35 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/client/ngapp/views/nettests/tcp-connect.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Details for this specific test:
5 |
6 |
7 |
11 |
12 |
15 |
16 |
19 |
20 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
TCP Connect
30 |
This test attempts to connect to a particular endpoint, such as
31 | a website, and it sees if it's able to connect. If it's not able
32 | to, then that's a sign of network tampering.
33 |
34 |
35 |
36 | Note: False positives can occur when the endpoint is down.
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/client/ngapp/views/nettests/web-connectivity.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | This measurement contains data that could be a sign of network tampering or censorship.
6 |
7 |
8 | This measurement looks normal.
9 |
10 |
11 |
22 |
23 |
24 |
25 |
Web Connectivity
26 |
This test examines whether websites are reachable and if they are not, it
27 | attempts to determine whether access to them is blocked through DNS tampering,
28 | TCP connection RST/IP blocking or by a transparent HTTP proxy.
29 |
30 |
Specifically, this test is designed to perform the following:
31 |
32 |
33 | Resolver identification
34 |
35 | DNS lookup
36 |
37 | TCP connect
38 |
39 | HTTP GET request
40 |
41 |
42 |
By default, this test performs the above (excluding the first step, which is
43 | performed only over the network of the user) both over a control server and over
44 | the network of the user. If the results from both networks match, then there is
45 | no clear sign of network interference; but if the results are different, then
46 | the websites that the user is testing are likely censored.
47 |
48 |
Below we provide information about how each step performed under the web
49 | connectivity test works.
50 |
51 |
1. Resolver identification
52 |
53 |
The domain name system (DNS) is what is responsible for transforming a host name
54 | (e.g. torproject.org) into an IP address (e.g. 38.229.72.16). Internet Service
55 | Providers, amongst others, run DNS resolvers which map IP addresses to host
56 | names. In some circumstances though, ISPs map the requested host names to the
57 | wrong IP addresses, which is a form of tampering.
58 |
59 |
As a first step, the web connectivity test attempts to identify which DNS
60 | resolver is being used by the user. It does so by performing a DNS query to
61 | special domains (such as whoami.akamai.com) which will disclose the IP address
62 | of the resolver.
63 |
64 |
2. DNS lookup
65 |
66 |
Once the web connectivity test has identified the DNS resolver of the user, it
67 | then attempts to identify which addresses and are mapped to the tested host
68 | names by the resolver. It does so by performing a DNS lookup, which asks the
69 | resolver to disclose which IP addresses are mapped to the tested host names, as
70 | well as which other host names are linked to the tested host names under DNS
71 | queries.
72 |
73 |
3. TCP connect
74 |
75 |
The web connectivity test will then try to connect to the tested websites by
76 | attempting to establish a TCP session on port 80 (or port 443 for URLs that
77 | begin with HTTPS) for the list of IP addresses that were identified in the
78 | previous step (DNS lookup).
79 |
80 |
4. HTTP GET request
81 |
82 |
As the web connectivity test connects to tested websites (through the previous
83 | step), it sends requests through the HTTP protocol to the servers which are
84 | hosting those websites. A server normally responds to an HTTP GET request with
85 | the content of the webpage that is requested.
86 |
87 |
Comparison of results: Identifying censorship
88 |
89 |
Once the above steps of the web connectivity test are performed *both* over a
90 | control server and over the network of the user, the collected results are then
91 | compared with the aim of identifying whether and how tested websites are
92 | tampered with. If the compared results do *not* match, then there is a sign of
93 | network interference.
94 |
95 |
Below are the conditions under which the following types of blocking are
96 | identified:
97 |
98 |
99 | DNS blocking: If the DNS responses (such as the IP addresses mapped to
100 | host names) do not match
101 |
102 | TCP/IP blocking: If a TCP session to connect to websites was not
103 | established over the network of the user
104 |
105 | HTTP blocking: If the HTTP request over the user's network failed, or the
106 |
107 | HTTP status codes don't match, or all of the following apply:
108 |
109 | The body length of compared websites (over the control server and the
110 | network of the user) differs by some percentage
111 | The HTTP headers names do not match
112 | The HTML title tags do not match
113 |
114 |
115 |
116 | The examples below (testing piratebay.se and google.com for censorship in Italy) show
117 | what the output of the web connectivity test could look like:
118 |
119 | Note: DNS resolvers, such as Google or your local ISP, often provide users
120 | with IP addresses that are closest to them geographically. Often this is not
121 | done with the intent of network tampering, but merely for the purpose of
122 | providing users faster access to websites. As a result, some false positives
123 | might arise in OONI measurements. Other false positives might occur when tested
124 | websites serve different content depending on the country that the user is
125 | connecting from, or in the cases when websites return failures even though they
126 | are not tampered with.
127 |
128 |
129 |
130 |
131 |
132 |
133 |
DNS query answers
134 |
Resolver: {{ report.test_keys.client_resolver }}
135 |
Answers
136 |
137 |
138 | {{answer.ipv4}}
139 |
140 |
141 |
142 |
143 |
144 |
HTTP Response Body
145 |
149 |
150 |
expand ›
151 |
152 |
153 |
154 |
TCP Connect results
155 |
168 |
169 |
170 |
171 |
172 |
173 |
--------------------------------------------------------------------------------
/client/ngapp/views/view-measurement.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
404 - Report Not Found
5 |
This can either mean that the report you submitted has not yet been
6 | processed or that it was not submitted successfully. Please try again later.
7 |
Go Back Home
8 |
9 |
10 |
68 |
69 |
--------------------------------------------------------------------------------
/client/ngapp/views/website-view.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
Category:
10 |
We started measuring this URL on .
11 |
12 |
13 | The URL was suggested for measuring by .
14 |
15 |
16 |
22 |
23 | Anomalous measurements Run on the URL:
24 |
25 |
26 |
27 |
28 |
29 |
30 | {{ measurement.test_start_time | date:'shortDate' }}: view »
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/client/ngapp/views/world.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
Measurements
9 |
10 |
11 | More than 100,000
12 |
13 |
14 | More than 10,000
15 |
16 |
17 | Less than 10,000
18 |
19 |
20 |
Discoveries
21 |
22 |
25 |
26 | Vendors identified
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
Country List
39 |
40 | Browse countries to find the one you want to investigate measurements for
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/common/models/censorship_method.js:
--------------------------------------------------------------------------------
1 | module.exports = function(CensorshipMethod) {
2 |
3 | };
4 |
--------------------------------------------------------------------------------
/common/models/censorship_method.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "censorship_method",
3 | "plural": "censorship_methods",
4 | "base": "PersistedModel",
5 | "idInjection": false,
6 | "options": {
7 | "validateUpsert": true,
8 | "postgresql": {
9 | "table": "censorship_methods"
10 | }
11 | },
12 | "properties": {
13 | "id": {
14 | "type": "Number",
15 | "id": true
16 | },
17 | "name": {
18 | "type": "String"
19 | },
20 | "description": {
21 | "type": "String"
22 | }
23 | },
24 | "validations": [],
25 | "relations": {},
26 | "acls": [],
27 | "methods": {}
28 | }
29 |
--------------------------------------------------------------------------------
/common/models/country.js:
--------------------------------------------------------------------------------
1 | module.exports = function(Country) {
2 |
3 | };
4 |
--------------------------------------------------------------------------------
/common/models/country.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "country",
3 | "plural": "countries",
4 | "base": "PersistedModel",
5 | "idInjection": false,
6 | "options": {
7 | "validateUpsert": true
8 | },
9 | "properties": {
10 | "id": {
11 | "type": "Number",
12 | "id": true
13 | },
14 | "name": {
15 | "type": "String"
16 | },
17 | "iso_alpha2": {
18 | "type": "String"
19 | },
20 | "iso_alpha3": {
21 | "type": "String"
22 | }
23 | },
24 | "validations": [],
25 | "relations": {
26 | "censorship_methods": {
27 | "type": "hasAndBelongsToMany",
28 | "model": "censorship_method",
29 | "foreignKey": ""
30 | }
31 | },
32 | "acls": [],
33 | "methods": {}
34 | }
35 |
--------------------------------------------------------------------------------
/common/models/nettest.js:
--------------------------------------------------------------------------------
1 | module.exports = function(Nettest) {
2 |
3 | };
4 |
--------------------------------------------------------------------------------
/common/models/nettest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nettest",
3 | "plural": "nettests",
4 | "base": "PersistedModel",
5 | "idInjection": true,
6 | "options": {
7 | "validateUpsert": true
8 | },
9 | "properties": {
10 | "long_name": {
11 | "type": "string",
12 | "required": true
13 | },
14 | "name": {
15 | "type": "string",
16 | "required": true
17 | },
18 | "description": {
19 | "type": "string",
20 | "required": true
21 | },
22 | "spec_url": {
23 | "type": "string",
24 | "required": true
25 | }
26 | },
27 | "validations": [],
28 | "relations": {},
29 | "acls": [],
30 | "methods": {}
31 | }
32 |
--------------------------------------------------------------------------------
/common/models/report.js:
--------------------------------------------------------------------------------
1 | var axios = require('axios')
2 | var qs = require('qs')
3 |
4 | var countries = require('country-data').countries
5 |
6 | var apiClient = axios.create({
7 | baseURL: 'https://api.ooni.io/api/', // yes, that's hardcoded
8 | timeout: 90000, // Maybe set this lower once performance is boosted
9 | })
10 |
11 | module.exports = function(Report) {
12 |
13 | Report.findMeasurements = function(probe_cc, input, order, page_number,
14 | page_size, since, until, test_name, report_id, callback) {
15 | var apiQuery = {}
16 | if (probe_cc) {
17 | apiQuery.probe_cc = probe_cc
18 | }
19 | if (input) {
20 | apiQuery.input = input
21 | }
22 | if (report_id) {
23 | apiQuery.report_id = report_id
24 | }
25 | if (test_name) {
26 | apiQuery.test_name = test_name
27 | }
28 |
29 | if (order) {
30 | apiQuery.order_by = order.split(' ')[0]
31 | apiQuery.order = order.split(' ')[1]
32 | }
33 |
34 | if (page_number && page_size) {
35 | apiQuery.offset = page_number * page_size
36 | apiQuery.limit = page_size
37 | }
38 | if (since) {
39 | apiQuery.since= since
40 | }
41 | if (until) {
42 | apiQuery.until = until
43 | }
44 |
45 | apiClient.get(`/v1/measurements?${qs.stringify(apiQuery)}`)
46 | .then(function(response) {
47 | // This is a workaround
48 | callback(null, response.data.results.map(function(row) {
49 | row['test_start_time'] = row['measurement_start_time']
50 | return row
51 | }))
52 | })
53 | .catch(function(error) {
54 | callback(error, null);
55 | })
56 | }
57 | Report.remoteMethod(
58 | 'findMeasurements',
59 | { http: { verb: 'get' },
60 | description: 'Returns the list of measurements matching the query',
61 | accepts: [
62 | {arg: 'probe_cc', type: 'string'},
63 | {arg: 'input', type: 'string'},
64 | {arg: 'order', type: 'string'},
65 | {arg: 'page_number', type: 'string'},
66 | {arg: 'page_size', type: 'string'},
67 | {arg: 'since', type: 'string'},
68 | {arg: 'until', type: 'string'},
69 | {arg: 'test_name', type: 'string'},
70 | {arg: 'report_id', type: 'string'}
71 | ],
72 | returns: {arg: 'data', type: ['Object'], root: true}
73 | }
74 | );
75 |
76 |
77 | Report.blockpageList = function(probe_cc, callback) {
78 | var apiQuery = {
79 | probe_cc: probe_cc
80 | }
81 | apiClient.get(`/_/blockpages?${qs.stringify(apiQuery)}`)
82 | .then(function(response) {
83 | callback(null, response.data.results)
84 | })
85 | .catch(function(error) {
86 | callback(error, null);
87 | })
88 | }
89 |
90 | Report.remoteMethod(
91 | 'blockpageList',
92 | { http: { verb: 'get' },
93 | description: 'Returns the list of URLs that appear to be blocked in a given country',
94 | accepts: {arg: 'probe_cc', type: 'string'},
95 | returns: {arg: 'data', type: ['Object'], root: true}
96 | }
97 | );
98 |
99 | Report.total = function(callback) {
100 | apiClient.get(`/_/measurement_count_total`)
101 | .then(function(response) {
102 | callback(null, response.data)
103 | })
104 | .catch(function(error) {
105 | callback(error, null);
106 | })
107 | }
108 |
109 | Report.remoteMethod(
110 | 'total',
111 | { http: { verb: 'get' },
112 | description: 'Returns the total number of measurements collected',
113 | returns: {arg: 'data', type: 'Object', root: true}
114 | }
115 | );
116 |
117 | Report.websiteDetails = function (website_url, callback) {
118 | var ds = Report.dataSource
119 | var wildcard_url = '%' + website_url
120 |
121 | var sql = 'SELECT * FROM domains WHERE url LIKE $1'
122 | ds.connector.query(sql, [wildcard_url], callback)
123 | }
124 |
125 | Report.remoteMethod(
126 | 'websiteDetails',
127 | { http: { verb: 'get' },
128 | description: 'Returns website details',
129 | accepts: {arg: 'website_url', type: 'string'},
130 | returns: {arg: 'data', type: ['Object'], root: true}
131 | }
132 | )
133 |
134 | Report.asnName = function (asn, callback) {
135 | var ds = Report.dataSource
136 |
137 | var sql = 'SELECT name FROM asns WHERE asn = $1'
138 | ds.connector.query(sql, [asn], callback)
139 | }
140 |
141 | Report.remoteMethod(
142 | 'asnName',
143 | { http: { verb: 'get' },
144 | description: 'Returns ASN name',
145 | accepts: {arg: 'asn', type: 'string'},
146 | returns: {arg: 'data', type: ['Object'], root: true}
147 | }
148 | )
149 |
150 | Report.websiteMeasurements = function (website_url, callback) {
151 | var apiQuery = {
152 | input: website_url
153 | }
154 | apiClient.get(`/_/website_measurements?${qs.stringify(apiQuery)}`)
155 | .then(function(response) {
156 | callback(null, response.data.results)
157 | })
158 | .catch(function(error) {
159 | callback(error, null);
160 | })
161 | }
162 |
163 | Report.remoteMethod(
164 | 'websiteMeasurements',
165 | { http: { verb: 'get' },
166 | description: 'Returns website\'s measurements',
167 | accepts: {arg: 'website_url', type: 'string'},
168 | returns: {arg: 'data', type: ['Object'], root: true}
169 | }
170 | )
171 |
172 | Report.vendors = function(probe_cc, callback) {
173 | var ds = Report.dataSource;
174 | var sql = "SELECT * FROM identified_vendors";
175 |
176 | if (typeof(probe_cc) !== "undefined"){
177 | sql += " WHERE probe_cc = $1";
178 | ds.connector.query(sql, [probe_cc], callback);
179 | } else {
180 | ds.connector.query(sql, callback);
181 | }
182 |
183 | }
184 |
185 | Report.remoteMethod(
186 | 'vendors',
187 | { http: { verb: 'get' },
188 | description: 'Returns the identified vendors of censorship and surveillance equipment',
189 | accepts: {arg: 'probe_cc', type: 'string'},
190 | returns: {arg: 'data', type: ['Object'], root: true}
191 | }
192 | );
193 |
194 | Report.blockpageDetected = function(callback) {
195 | apiClient.get(`/_/blockpage_detected`)
196 | .then(function(response) {
197 | callback(null, response.data.results)
198 | })
199 | .catch(function(error) {
200 | callback(error, null);
201 | })
202 | }
203 |
204 | Report.remoteMethod(
205 | 'blockpageDetected',
206 | { http: { verb: 'get' },
207 | description: 'Returns the country codes where we detected the presence of a blockpage',
208 | returns: { arg: 'data', type: ['Object'], root: true }
209 | }
210 | );
211 |
212 | Report.blockpageCount = function(probe_cc, callback) {
213 | var apiQuery = {
214 | probe_cc: probe_cc
215 | }
216 | apiClient.get(`/_/blockpage_count?${qs.stringify(apiQuery)}`)
217 | .then(function(response) {
218 | callback(null, response.data.results)
219 | })
220 | .catch(function(error) {
221 | callback(error, null);
222 | })
223 | }
224 |
225 | Report.remoteMethod(
226 | 'blockpageCount',
227 | { http: { verb: 'get' },
228 | description: 'Returns the number of blockpages detected per total',
229 | accepts: {arg: 'probe_cc', type: 'string'},
230 | returns: { arg: 'data', type: ['Object'], root: true }
231 | }
232 | );
233 |
234 | Report.countByCountry = function(callback) {
235 | apiClient.get(`/_/measurement_count_by_country`)
236 | .then(function(response) {
237 | var result = [];
238 | response.data.results.forEach(function(row) {
239 | var country = countries[row['probe_cc']];
240 | if (country !== undefined) {
241 | result.push({
242 | name: country.name,
243 | alpha3: country.alpha3,
244 | alpha2: country.alpha2,
245 | count: row['count']
246 | });
247 | }
248 | });
249 | callback(null, result)
250 | })
251 | .catch(function(error) {
252 | callback(error, null);
253 | })
254 | }
255 |
256 | Report.remoteMethod(
257 | 'countByCountry',
258 | { http: { verb: 'get' },
259 | description: 'Get number of reports by country code',
260 | accepts: [],
261 | returns: { arg: 'data', type: ['Object'], root: true }
262 | }
263 | );
264 | };
265 |
--------------------------------------------------------------------------------
/common/models/report.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "report",
3 | "base": "PersistedModel",
4 | "idInjection": false,
5 | "options": {
6 | "validateUpsert": true,
7 | "postgresql": {
8 | "table": "metrics"
9 | }
10 | },
11 | "properties": {
12 | "id": {
13 | "type": "String",
14 | "postgresql": {
15 | "columnName": "report_id"
16 | }
17 | },
18 | "input": {
19 | "type": "String"
20 | },
21 | "options": {
22 | "type": "Object"
23 | },
24 | "probe_asn": {
25 | "type": "String"
26 | },
27 | "probe_cc": {
28 | "type": "String",
29 | "required": true
30 | },
31 | "probe_ip": {
32 | "type": "String"
33 | },
34 | "software_name": {
35 | "type": "String"
36 | },
37 | "software_version": {
38 | "type": "String"
39 | },
40 | "report_filename": {
41 | "type": "String"
42 | },
43 | "test_start_time": {
44 | "type": "Date"
45 | },
46 | "test_runtime": {
47 | "type": "Number"
48 | },
49 | "measurement_start_time": {
50 | "type": "Date"
51 | },
52 | "test_name": {
53 | "type": "String",
54 | "required": true
55 | },
56 | "data_format_version": {
57 | "type": "String"
58 | },
59 | "test_helpers": {
60 | "type": "Object"
61 | },
62 | "test_keys": {
63 | "type": "Object"
64 | }
65 | },
66 | "validations": [],
67 | "relations": {
68 | "country": {
69 | "type": "belongsTo",
70 | "model": "country",
71 | "foreignKey": "probe_cc"
72 | }
73 | },
74 | "acls": [],
75 | "methods": {}
76 | }
77 |
--------------------------------------------------------------------------------
/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Script used to deploy to heroku
3 | set -e
4 |
5 | git branch -D deploy || echo "First deployment"
6 | git checkout -b deploy
7 | grunt build:production
8 | git add -f client/dist/
9 | git commit -m "Deploying to Heroku"
10 | git push heroku -f deploy:master
11 | git checkout master
12 |
--------------------------------------------------------------------------------
/docs/deployment.md:
--------------------------------------------------------------------------------
1 | # Setup
2 |
3 | The production deployment relies on systemd for running the node service and is
4 | fronted by nginx.
5 |
6 | The requirements for deploying ooni-explorer in production are the same as those
7 | for running it in a development environment.
8 |
9 | You will need to have installed recent versions of:
10 |
11 | * [Node with npm](https://nodejs.org/en/download/)
12 |
13 | * [Bower](http://bower.io/#install-bower)
14 |
15 | Once these are installed you should setup a user to run the explorer as.
16 |
17 | You should clone the repository of the explorer in the home of the user:
18 |
19 | ```
20 | git clone https://github.com/TheTorProject/ooni-explorer.git
21 | ```
22 |
23 | Install the node and bower requirements:
24 |
25 | ```
26 | bower install
27 | npm install
28 | ```
29 |
30 | Edit the `server/datasources.production.js` to include the details of your
31 | database.
32 |
33 | To build the production version of the application run:
34 |
35 | ```
36 | grunt build
37 | ```
38 |
39 | Then configure a systemd service with a file like this (assuming the user is
40 | called ooni-explorer):
41 |
42 | `$ cat /etc/systemd/system/node-ooni-explorer.service`
43 | ```
44 | [Service]
45 | ExecStart=/usr/bin/node /home/ooni-explorer/ooni-explorer/server.js
46 | Restart=always
47 | StandardOutput=syslog
48 | StandardError=syslog
49 | SyslogIdentifier=node-ooni-explorer
50 | User=ooni-explorer
51 | Group=ooni-explorer
52 | Environment=NODE_ENV=production
53 |
54 | [Install]
55 | WantedBy=multi-user.target
56 | ```
57 |
58 | By default ooni-explorer will bind on port 3000, so you can either use nginx to
59 | proxy connection to port 3000 (recommended) or use an iptables rule if you don't
60 | need TLS.
61 |
62 | This is how the nginx configuration should look like
63 | (`/etc/nginx/sites-enabled/ooni-explorer`):
64 |
65 | ```
66 | server {
67 | listen 80;
68 | server_name explorer.ooni.torproject.org explorer.ooni.io;
69 |
70 | location / {
71 | proxy_pass http://127.0.0.1:3000;
72 | proxy_http_version 1.1;
73 | proxy_set_header Upgrade $http_upgrade;
74 | proxy_set_header Connection 'upgrade';
75 | proxy_set_header Host $host;
76 | proxy_cache_bypass $http_upgrade;
77 | }
78 | }
79 | server {
80 | # SSL configuration
81 | listen 443 ssl;
82 | server_name explorer.ooni.torproject.org explorer.ooni.io;
83 | ssl_certificate /path/to/fullchain.pem;
84 | ssl_certificate_key /path/to/privkey.pem;
85 |
86 | location / {
87 | proxy_pass http://127.0.0.1:3000;
88 | proxy_http_version 1.1;
89 | proxy_set_header Upgrade $http_upgrade;
90 | proxy_set_header Connection 'upgrade';
91 | proxy_set_header Host $host;
92 | proxy_cache_bypass $http_upgrade;
93 | }
94 | }
95 | ```
96 |
97 | # Starting and stopping
98 |
99 | To start the service run:
100 |
101 | ```
102 | service node-ooni-explorer start
103 | ```
104 |
105 | To stop the service run:
106 |
107 | ```
108 | service node-ooni-explorer stop
109 | ```
110 |
111 | # Updating
112 |
113 | ```
114 | cd /home/ooni-explorer/ooni-explorer/
115 | git pull
116 | grunt build
117 | service node-ooni-explorer restart
118 | ```
119 |
--------------------------------------------------------------------------------
/global-config.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Global configuration shared by components.
3 | */
4 |
5 | var url = require('url');
6 |
7 | var conf = {
8 | hostname: 'localhost',
9 | port: 3000,
10 | restApiRoot: '/api', // The path where to mount the REST API app
11 | legacyExplorer: false
12 | };
13 |
14 | // The URL where the browser client can access the REST API is available.
15 | // Replace with a full url (including hostname) if your client is being
16 | // served from a different server than your REST API.
17 | conf.restApiUrl = url.format({
18 | protocol: 'http',
19 | slashes: true,
20 | hostname: conf.hostname,
21 | port: conf.port,
22 | pathname: conf.restApiRoot
23 | });
24 |
25 | module.exports = conf;
26 |
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | print_usage () { ###1
3 | echo "Valid options are OS={debian,centos,osx} "
4 | echo "For example if you are running CentOS you can try:"
5 | printf '\tOS=centos sh install.sh'
6 | }
7 | npm_install () { ###1
8 | NPM_GLOBAL_DEPENDENCIES='jshint bower grunt-cli strongloop'
9 |
10 | echo "Installing npm global dependencies..."
11 | npm install -g $NPM_GLOBAL_DEPENDENCIES || sudo npm install -g $NPM_GLOBAL_DEPENDENCIES
12 |
13 | echo "Installing npm development dependencies..."
14 | npm install --development
15 | }
16 | ###1 Parse arguments
17 | while [ $# -gt 0 ]; do
18 | case "$1" in
19 | -h|--help)
20 | print_usage
21 | exit 0
22 | ;;
23 | -d|--debug)
24 | set -xe
25 | ;;
26 | esac
27 | shift
28 | done
29 |
30 | ###1 Determine the OS
31 | case "$OS" in
32 | "debian"|"centos"|"osx")
33 | # accept manually set values
34 | ;;
35 | *)
36 | [ -n "$OS" ] && >&2 echo "Ignoring invalid OS=${OS}"
37 | if type uname >/dev/null 2>/dev/null; then
38 | case "$(uname)" in
39 | Darwin)
40 | OS="osx"
41 | ;;
42 | Linux)
43 | if type lsb_release >/dev/null 2>/dev/null; then
44 | # n.b.: lsb_release is not a default package
45 | case "$(lsb_release -i)" in
46 | *Debian)
47 | OS="debian"
48 | ;;
49 | *Centos)
50 | OS="centos"
51 | ;;
52 | esac
53 | elif [ -f "/etc/redhat-release" || -f "/etc/centos-release" ]; then
54 | OS="centos"
55 | elif [ -f "/etc/os-release" ]; then
56 | if grep debian /etc/os-release >/dev/null 2>/dev/null; then
57 | OS="debian"
58 | fi
59 | fi
60 | ;;
61 | esac
62 | fi
63 | ;;
64 | esac
65 |
66 | ###1 Installation action
67 | case "$OS" in
68 | "debian")
69 | sudo apt-get update
70 |
71 | echo "Installing build-essential..."
72 | sudo apt-get install -y build-essential
73 |
74 | echo "Installing Node..."
75 | sudo apt-get install -y npm nodejs nodejs-legacy
76 |
77 | npm_install
78 | ;;
79 | "centos")
80 | # This is tested on CentOS 7
81 |
82 | echo "Installing Node..."
83 | curl --silent --location https://rpm.nodesource.com/setup > install_node.sh
84 | chmod +x install_node.sh
85 | # XXX this is super ghetto
86 | sudo ./install_node.sh
87 |
88 | echo "Installing make dependencies"
89 | sudo yum install -y gcc-c++ make nodejs
90 |
91 | npm_install
92 | ;;
93 | "osx")
94 | if ! type node >/dev/null 2>/dev/null; then
95 | echo "Node not found. Installing node..."
96 | if ! type brew >/dev/null 2>/dev/null; then
97 | echo "This install script relies on Homebrew and it doesn't seem to be installed."
98 | printf "http://brew.sh\n\n"
99 | print_usage
100 | exit 4
101 | fi
102 | brew install node
103 | fi
104 |
105 | if ! type make >/dev/null 2>/dev/null; then
106 | echo "make doesn't seem to be installed"
107 | echo "You may want to install make and gcc with XCode or Homebrew."
108 | exit 5
109 | fi
110 |
111 | npm_install
112 | ;;
113 | *)
114 | echo "Unable to detect a supported operating system."
115 | print_usage
116 | exit 99
117 | ;;
118 | esac
119 | # vim: set ft=sh tw=0 ts=2 sw=2 sts=2 fdm=marker fmr=###,### et:
120 |
--------------------------------------------------------------------------------
/ooni-api-spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "swagger": "2.0",
3 | "info": {
4 | "version": "1.0.0",
5 | "title": "OONI Pipeline API",
6 | "description": "This is the main API to access the OONI data pieline",
7 | "termsOfService": "http://api.ooni.io/tos.md",
8 | "license": {
9 | "name": "MIT"
10 | }
11 | },
12 | "host": "api.ooni.io",
13 | "basePath": "/api",
14 | "schemes": [
15 | "http"
16 | ],
17 | "consumes": [
18 | "application/json"
19 | ],
20 | "produces": [
21 | "application/json"
22 | ],
23 | "paths": {
24 | "/reports": {
25 | "get": {
26 | "description": "Returns all the reports that have been collected",
27 | "operationId": "findReports",
28 | "produces": [
29 | "application/json",
30 | "application/xml",
31 | "text/xml",
32 | "text/html"
33 | ],
34 | "parameters": [
35 | {
36 | "name": "country_code",
37 | "in": "query",
38 | "description": "country codes to filter by",
39 | "required": false,
40 | "type": "array",
41 | "items": {
42 | "type": "string"
43 | },
44 | "collectionFormat": "csv"
45 | },
46 | {
47 | "name": "limit",
48 | "in": "query",
49 | "description": "maximum number of results to return",
50 | "required": false,
51 | "type": "integer",
52 | "format": "int32"
53 | }
54 | ],
55 | "responses": {
56 | "200": {
57 | "description": "report response",
58 | "schema": {
59 | "type": "array",
60 | "items": {
61 | "$ref": "#/definitions/Report"
62 | }
63 | }
64 | },
65 | "default": {
66 | "description": "unexpected error",
67 | "schema": {
68 | "$ref": "#/definitions/ErrorModel"
69 | }
70 | }
71 | }
72 | }
73 | },
74 | "/reports/{id}": {
75 | "get": {
76 | "description": "Returns a the report that matches the specified ID",
77 | "operationId": "findReportById",
78 | "produces": [
79 | "application/json",
80 | "application/xml",
81 | "text/xml",
82 | "text/html"
83 | ],
84 | "parameters": [
85 | {
86 | "name": "id",
87 | "in": "path",
88 | "description": "ID of report to fetch",
89 | "required": true,
90 | "type": "string"
91 | }
92 | ],
93 | "responses": {
94 | "200": {
95 | "description": "report response",
96 | "schema": {
97 | "$ref": "#/definitions/Report"
98 | }
99 | },
100 | "default": {
101 | "description": "unexpected error",
102 | "schema": {
103 | "$ref": "#/definitions/ErrorModel"
104 | }
105 | }
106 | }
107 | }
108 | }
109 | },
110 | "definitions": {
111 | "Report": {
112 | "required": [
113 | "probe_cc",
114 | "report_id",
115 | "test_name"
116 | ],
117 | "properties": {
118 | "backend_version": {
119 | "type": "string"
120 | },
121 | "input_hashes": {
122 | "type": "array", "items": "string"
123 | },
124 | "options": {
125 | "type": "string"
126 | },
127 | "probe_asn": {
128 | "type": "string"
129 | },
130 | "probe_cc": {
131 | "type": "string"
132 | },
133 | "probe_ip": {
134 | "type": "string"
135 | },
136 | "record_type": {
137 | "type": "string"
138 | },
139 | "report_filename": {
140 | "type": "string"
141 | },
142 | "report_id": {
143 | "type": "string"
144 | },
145 | "software_name": {
146 | "type": "string"
147 | },
148 | "software_version": {
149 | "type": "string"
150 | },
151 | "start_time": {
152 | "type": "string"
153 | },
154 | "test_name": {
155 | "type": "string"
156 | },
157 | "test_version": {
158 | "type": "string"
159 | },
160 | "data_format_version": {
161 | "type": "string"
162 | },
163 | "test_helpers": {
164 | "type": "string"
165 | }
166 | }
167 | }
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ooni-explorer",
3 | "version": "1.0.0",
4 | "license": "BSD-3-Clause",
5 | "main": "server/server.js",
6 | "scripts": {
7 | "pretest": "jshint .",
8 | "test": "grunt test",
9 | "build": "grunt build",
10 | "start": "node server.js",
11 | "postinstall": "node -e \"try { require('fs').symlinkSync(require('path').resolve('node_modules/@bower_components'), 'bower_components', 'junction') } catch (e) { }\""
12 | },
13 | "dependencies": {
14 | "@bower_components/angular": "angular/bower-angular#~1.4",
15 | "@bower_components/angular-datamaps": "git://github.com/dmachat/angular-datamaps.git#~0.1.0",
16 | "@bower_components/angular-daterangepicker": "git://github.com/fragaria/angular-daterangepicker.git#~0.2.2",
17 | "@bower_components/angular-inview": "thenikso/angular-inview#~1.5.6",
18 | "@bower_components/angular-mocks": "angular/bower-angular-mocks#~1.4",
19 | "@bower_components/angular-resource": "angular/bower-angular-resource#~1.4",
20 | "@bower_components/angular-route": "angular/bower-angular-route#~1.4",
21 | "@bower_components/angular-scenario": "angular/bower-angular-scenario#~1.4",
22 | "@bower_components/angular-typewrite": "antoniocapelo/Angular-Typewrite#~0.0.14",
23 | "@bower_components/angular-ui-codemirror": "git://github.com/angular-ui/ui-codemirror.git#~0.3.0",
24 | "@bower_components/angular-ui-grid": "git://github.com/angular-ui/bower-ui-grid.git#~3.0.0-rc.21",
25 | "@bower_components/bootstrap": "git://github.com/twbs/bootstrap.git#^3.0.0",
26 | "@bower_components/bootstrap-daterangepicker": "dangrossman/bootstrap-daterangepicker#^2.0.0",
27 | "@bower_components/codemirror": "https://registry.yarnpkg.com/codemirror/-/codemirror-5.22.0.tgz",
28 | "@bower_components/colorbrewer": "git://github.com/undashes/colorbrewer.git#~1.0.0",
29 | "@bower_components/d3": "mbostock-bower/d3-bower#^3.5.6",
30 | "@bower_components/datamaps": "markmarkoh/datamaps#~0.4.0",
31 | "@bower_components/es5-shim": "git://github.com/es-shims/es5-shim.git#~3.1.0",
32 | "@bower_components/factbook-country-data": "git://github.com/simonv3/factbook-country-data#0cceccd6e393e6a860798e0b7e5be28c4f18efd4",
33 | "@bower_components/flag-icon-css": "lipis/flag-icon-css#*",
34 | "@bower_components/font-awesome": "git://github.com/FortAwesome/Font-Awesome.git#~4.5.0",
35 | "@bower_components/iso-3166-country-codes-angular": "git://github.com/hellais/iso-3166-country-codes-angular#611d0cf4dc2244a74cd337c516744d1dce235e7c",
36 | "@bower_components/jquery": "jquery/jquery-dist#1.9.1 - 2",
37 | "@bower_components/json-formatter": "git://github.com/mohsen1/json-formatter.git#~0.4.2",
38 | "@bower_components/json3": "git://github.com/bestiejs/json3.git#~3.3.1",
39 | "@bower_components/moment": "moment/moment#>=2.9.0",
40 | "@bower_components/topojson": "git://github.com/mbostock/topojson.git#1.6.20",
41 | "async": "~2.1.4",
42 | "axios": "^0.16.2",
43 | "compression": "^1.0.3",
44 | "cors": "^2.5.2",
45 | "country-data": "0.0.31",
46 | "errorhandler": "^1.1.1",
47 | "loopback": "^2.14.0",
48 | "loopback-boot": "^2.6.5",
49 | "loopback-component-storage": "~1.0.5",
50 | "loopback-connector-postgresql": "~2.1.0",
51 | "loopback-datasource-juggler": "^2.19.0",
52 | "node-sass": "^4.5.3",
53 | "proquint": "0.0.1",
54 | "qs": "^6.5.1",
55 | "serve-favicon": "^2.0.1"
56 | },
57 | "optionalDependencies": {
58 | "loopback-explorer": "^1.1.0"
59 | },
60 | "devDependencies": {
61 | "bower": "^1.3.8",
62 | "browserify": "~4.2.3",
63 | "connect-livereload": "^0.4.0",
64 | "grunt": "^0.4.5",
65 | "grunt-autoprefixer": "^0.8.2",
66 | "grunt-cli": "",
67 | "grunt-concurrent": "^0.5.0",
68 | "grunt-contrib-clean": "^0.5.0",
69 | "grunt-contrib-concat": "^0.5.0",
70 | "grunt-contrib-connect": "^0.8.0",
71 | "grunt-contrib-copy": "^0.5.0",
72 | "grunt-contrib-cssmin": "^0.10.0",
73 | "grunt-contrib-htmlmin": "^0.3.0",
74 | "grunt-contrib-imagemin": "^0.8.1",
75 | "grunt-contrib-jshint": "^0.10.0",
76 | "grunt-contrib-uglify": "^0.5.1",
77 | "grunt-contrib-watch": "^0.6.1",
78 | "grunt-filerev": "^0.2.1",
79 | "grunt-google-cdn": "^0.4.0",
80 | "grunt-karma": "^0.8.3",
81 | "grunt-mocha-test": "^0.12.6",
82 | "grunt-newer": "^0.7.0",
83 | "grunt-ng-annotate": "^1.0.1",
84 | "grunt-sass": "^1.1.0",
85 | "grunt-svgmin": "^0.4.0",
86 | "grunt-usemin": "^2.3.0",
87 | "grunt-wiredep": "^1.8.0",
88 | "http-perf": "0.0.5",
89 | "jshint-stylish": "^0.4.0",
90 | "karma": "^0.12.17",
91 | "karma-chrome-launcher": "^0.1.4",
92 | "karma-jasmine": "^0.1.5",
93 | "load-grunt-tasks": "^0.6.0",
94 | "loopback-testing": "^1.0.4",
95 | "mocha": "^2.1.0",
96 | "time-grunt": "^0.4.0"
97 | },
98 | "repository": {
99 | "type": "",
100 | "url": ""
101 | },
102 | "description": "ooni-explorer",
103 | "engines": {
104 | "yarn": ">= 1.0.0"
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | server/server.js
--------------------------------------------------------------------------------
/server/boot/angular-routes.js:
--------------------------------------------------------------------------------
1 | module.exports = function(app) {
2 | var routes = require('../../client/ngapp/config/routes');
3 | Object
4 | .keys(routes)
5 | .forEach(function(route) {
6 | app.get(route, function(req, res) {
7 | res.sendFile(app.get('indexFile'));
8 | });
9 | });
10 | };
11 |
--------------------------------------------------------------------------------
/server/boot/authentication.js:
--------------------------------------------------------------------------------
1 | module.exports = function enableAuthentication(server) {
2 | // enable authentication
3 | server.enableAuth();
4 | };
5 |
--------------------------------------------------------------------------------
/server/boot/create-default-tables.js:
--------------------------------------------------------------------------------
1 | var async = require('async');
2 | module.exports = function(app) {
3 | var ds = app.dataSources.db;
4 |
5 | async.series([
6 | createNettestRows,
7 | createCensorshipMethods,
8 | createCountryRows
9 | ], function(err, result) {
10 | if (err) { console.log(err);return }
11 | console.log("Mapping method to country");
12 | mapMethodToCountry(function(err, result) {
13 | if (err) { console.log(err);return }
14 | console.log("Mapping done");
15 | });
16 | });
17 |
18 | function createCountryRows(cb) {
19 | var country_rows = [];
20 | var countries = require('country-data').countries;
21 | countries.all.forEach(function(country, idx) {
22 | if (country.alpha2 === 'TW') {
23 | country.name = 'Taiwan';
24 | }
25 | country_rows.push({
26 | 'iso_alpha2': country.alpha2,
27 | 'iso_alpha3': country.alpha3,
28 | 'name': country.name,
29 | 'id': idx
30 | });
31 | });
32 | console.log('Inserting country data');
33 | ds.automigrate('country', function(err) {
34 | if (err) return cb(err);
35 | console.log('Automigrated country data');
36 | app.models.country.create(country_rows, cb);
37 | });
38 | }
39 |
40 | function mapMethodToCountry(cb) {
41 | var dns_hijacking = 1;
42 | var http_proxy = 2;
43 | var tcp_ip = 3;
44 |
45 | var methodsByCountry = {
46 | 'TR': [dns_hijacking],
47 | 'IR': [dns_hijacking,http_proxy],
48 | 'SA': [http_proxy],
49 | 'ID': [http_proxy],
50 | 'CN': [dns_hijacking,tcp_ip],
51 | 'RU': [http_proxy],
52 | 'GR': [dns_hijacking,http_proxy]
53 | }
54 |
55 | /* This horror of spaghetti code is the result of many hours of debugging loopback and trying to decipher it's cryptic error messages just so that I could write 2 integers inside of a table.
56 | * This is not what a ORM is supposed to do.
57 | * I am not going to fix this.
58 | * */
59 | async.mapValues(methodsByCountry, function(methods, iso_alpha2, cb2) {
60 | console.log("" + iso_alpha2 + "->" + methods);
61 | app.models.country.findOne({'where': {'iso_alpha2': iso_alpha2}}, function(err, country) {
62 | if (err) {console.log(err); return cb2(err)}
63 | async.map(methods, function(methodId) {
64 | app.models.censorship_method.findById(methodId, function(err, method) {
65 | country.censorship_methods.add(method);
66 | });
67 | }, function(err, results) {
68 | if (err) return cb2(err);
69 | cb2(null, results);
70 | })
71 | });
72 | }, function(err, results) {
73 | if (err) {return cb(err)}
74 | cb(null, results);
75 | });
76 | }
77 |
78 | function createCensorshipMethods(cb) {
79 | var methods = [{
80 | 'id': 1,
81 | 'name': 'DNS hijacking',
82 | 'description': 'DNS hijacking involves sending falsified DNS query responses to requests sent by clients'
83 | },{
84 | 'id': 2,
85 | 'name': 'Transparent HTTP proxy',
86 | 'description': 'A transparent HTTP proxy is a middle box that will intercept the requests of users and either block them or display an error message'
87 | },{
88 | 'id': 3,
89 | 'name': 'TCP/IP blocking',
90 | 'description': 'TCP/IP based blocking is blocking or impeding the ability of a client to connect to the server'
91 | }];
92 | ds.automigrate(['censorship_method', 'countrycensorship_method'], function(err) {
93 | if (err) return cb(err);
94 | console.log('Automigrated censorship methods');
95 | app.models.censorship_method.create(methods, cb);
96 | });
97 | }
98 |
99 | function createNettestRows(cb) {
100 | var nettest_rows = [
101 | {
102 | 'name': 'bridget',
103 | 'long_name': 'BridgeT',
104 | 'description': 'Test to measure reachability of Tor bridges.',
105 | 'spec_url': 'https://github.com/TheTorProject/ooni-spec/blob/master/test-specs/ts-001-bridget.md'
106 | },
107 | {
108 | 'name': 'dns_consistency',
109 | 'long_name': 'DNS consistency',
110 | 'description': 'Test to measure consistency amongst DNS responses from a set of test resolvers and a control resolver.',
111 | 'spec_url': 'https://github.com/TheTorProject/ooni-spec/blob/master/test-specs/ts-002-dnsconsistency.md'
112 | },
113 | {
114 | 'name': 'http_requests',
115 | 'long_name': 'HTTP requests',
116 | 'description': 'Test to compare HTTP GET request responses from a control and experiment vantage point.',
117 | 'spec_url': 'https://github.com/TheTorProject/ooni-spec/blob/master/test-specs/ts-003-http-requests.md'
118 | },
119 |
120 | {
121 | 'name': 'http_host',
122 | 'long_name': 'HTTP host',
123 | 'description': 'Test to identify the presence of a transparent HTTP proxy and verify if certain censorship evasion technique will work against it.',
124 | 'spec_url': 'https://github.com/TheTorProject/ooni-spec/blob/master/test-specs/ts-004-httphost.md'
125 | },
126 | {
127 | 'name': 'dns_spoof',
128 | 'long_name': 'DNS spoof',
129 | 'description': 'Test to determine if DNS censorship is happening by means of DNS spoofing.',
130 | 'spec_url': 'https://github.com/TheTorProject/ooni-spec/blob/master/test-specs/ts-005-dnsspoof.md'
131 | },
132 | {
133 | 'name': 'http_header_field_manipulation',
134 | 'long_name': 'HTTP header field manipulation',
135 | 'description': 'Test to verify if the HTTP headers sent by the client are being altered when transmitted to the control backend.',
136 | 'spec_url': 'https://github.com/TheTorProject/ooni-spec/blob/master/test-specs/ts-006-header-field-manipulation.md'
137 | },
138 | {
139 | 'name': 'http_invalid_request_line',
140 | 'long_name': 'HTTP invalid request line',
141 | 'description': 'Sends invalid HTTP request lines in the attempt to trigger error responses from transparent HTTP proxies.',
142 | 'spec_url': 'https://github.com/TheTorProject/ooni-spec/blob/master/test-specs/ts-007-http-invalid-request-line.md'
143 | },
144 | {
145 | 'name': 'tcp_connect',
146 | 'long_name': 'TCP connect',
147 | 'description': 'Performs a TCP handshake with the endpoint.',
148 | 'spec_url': 'https://github.com/TheTorProject/ooni-spec/blob/master/test-specs/ts-008-tcpconnect.md'
149 | },
150 | {
151 | 'name': 'multi_protocol_traceroute',
152 | 'long_name': 'Multi protocol traceroute',
153 | 'description': 'Performs a ICMP, TCP and UDP traceroute with varying destination ports in an attempt to spot traceroute path biases.',
154 | 'spec_url': 'https://github.com/TheTorProject/ooni-spec/blob/master/test-specs/ts-009-multi-port-traceroute.md'
155 | },
156 | {
157 | 'name': 'captive_portal',
158 | 'long_name': 'Captive portal',
159 | 'description': 'Attempts to detect the presence of a captive portal',
160 | 'spec_url': 'https://github.com/TheTorProject/ooni-spec/blob/master/test-specs/ts-010-captive-portal.md'
161 | },
162 | {
163 | 'name': 'bridge_reachability',
164 | 'long_name': 'Bridge reachability',
165 | 'description': 'Tests accessibility of Tor bridges.',
166 | 'spec_url': 'https://github.com/TheTorProject/ooni-spec/blob/master/test-specs/ts-011-bridge-reachability.md'
167 | },
168 | {
169 | 'name': 'dns_injection',
170 | 'long_name': 'DNS injection',
171 | 'description': 'Tests to see if the answers to DNS queries for particular hostnames are being injected or spoofed',
172 | 'spec_url': 'https://github.com/TheTorProject/ooni-spec/blob/master/test-specs/ts-012-dns-injection.md'
173 | },
174 | {
175 | 'name': 'lantern_circumvention_tool_test',
176 | 'long_name': 'Lantern',
177 | 'description': 'Tests to see if the censorship circumvention tool lantern works.',
178 | 'spec_url': 'https://github.com/TheTorProject/ooni-spec/blob/master/test-specs/ts-012-lantern.md'
179 | },
180 | {
181 | 'name': 'meek_fronted_requests_test',
182 | 'long_name': 'Meek fronted requests',
183 | 'description': 'Tests the meek cloudfronted frontend to verify accessibility',
184 | 'spec_url': 'https://github.com/TheTorProject/ooni-spec/blob/master/test-specs/ts-013-meek-fronted-requests.md'
185 | },
186 |
187 | {
188 | 'name': 'psiphon_test',
189 | 'long_name': 'Psiphon',
190 | 'description': 'Tests to see if the censorship circumvention tool psiphon works.',
191 | 'spec_url': 'https://github.com/TheTorProject/ooni-spec/blob/master/test-specs/ts-014-psiphon.md'
192 | },
193 | {
194 | 'name': 'openvpn',
195 | 'long_name': 'OpenVPN',
196 | 'description': 'Tests to see if the openvpn client works with a set of openvpn endpoints',
197 | 'spec_url': 'https://github.com/TheTorProject/ooni-spec/blob/master/test-specs/ts-015-openvpn.md'
198 | },
199 | {
200 | 'name': 'vanilla_tor',
201 | 'long_name': 'Vanilla Tor',
202 | 'description': 'Test to see if tor with no bridges works',
203 | 'spec_url': 'https://github.com/TheTorProject/ooni-spec/blob/master/test-specs/ts-016-vanilla-tor.md'
204 | },
205 | {
206 | 'name': 'web_connectivity',
207 | 'long_name': 'Web Connectivity',
208 | 'description': 'Examines whether access to websites is blocked through DNS tampering, TCP or IP blocking, or by a transparent HTTP proxy',
209 | 'spec_url': 'https://github.com/TheTorProject/ooni-spec/blob/master/test-specs/ts-017-web-connectivity.md'
210 | },
211 | {
212 | 'name': 'whatsapp',
213 | 'long_name': 'WhatsApp',
214 | 'description': 'Checks to see if WhatsApp is working',
215 | 'spec_url': 'https://github.com/TheTorProject/ooni-spec/blob/master/test-specs/ts-018-whatsapp.md'
216 | },
217 | {
218 | 'name': 'facebook_messenger',
219 | 'long_name': 'Facebook Messenger',
220 | 'description': 'Checks to see if Facebook Messenger is working',
221 | 'spec_url': 'https://github.com/TheTorProject/ooni-spec/blob/master/test-specs/ts-019-facebook-messenger.md'
222 | },
223 | {
224 | 'name': 'telegram',
225 | 'long_name': 'Telegram',
226 | 'description': 'Checks to see if Telegram is working',
227 | 'spec_url': 'https://github.com/TheTorProject/ooni-spec/blob/master/test-specs/ts-020-telegram.md'
228 | },
229 |
230 | ];
231 | console.log('Inserting nettest data');
232 | ds.automigrate('nettest', function(err) {
233 | if (err) return cb(err);
234 | console.log('Automigrated nettest data');
235 | app.models.nettest.create(nettest_rows, cb);
236 | });
237 | }
238 |
239 | }
240 |
--------------------------------------------------------------------------------
/server/boot/dev-assets.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = function(app) {
4 | if (!app.get('isDevEnv')) return;
5 |
6 | var serveDir = app.loopback.static;
7 |
8 | app.use(serveDir(projectPath('.tmp')));
9 | app.use('/bower_components', serveDir(projectPath('bower_components')));
10 | app.use('/lbclient', serveDir(projectPath('client/lbclient')));
11 | };
12 |
13 | function projectPath(relative) {
14 | return path.resolve(__dirname, '../..', relative);
15 | }
16 |
--------------------------------------------------------------------------------
/server/boot/explorer.js:
--------------------------------------------------------------------------------
1 | module.exports = function mountLoopBackExplorer(server) {
2 | var explorer;
3 | try {
4 | explorer = require('loopback-explorer');
5 | } catch(err) {
6 | // Print the message only when the app was started via `server.listen()`.
7 | // Do not print any message when the project is used as a component.
8 | server.once('started', function(baseUrl) {
9 | console.log(
10 | 'Run `npm install loopback-explorer` to enable the LoopBack explorer'
11 | );
12 | });
13 | return;
14 | }
15 |
16 | var restApiRoot = server.get('restApiRoot');
17 |
18 | var explorerApp = explorer(server, { basePath: restApiRoot });
19 | server.use('/explorer', explorerApp);
20 | server.once('started', function() {
21 | var baseUrl = server.get('url').replace(/\/$/, '');
22 | // express 4.x (loopback 2.x) uses `mountpath`
23 | // express 3.x (loopback 1.x) uses `route`
24 | var explorerPath = explorerApp.mountpath || explorerApp.route;
25 | console.log('Browse your REST API at %s%s', baseUrl, explorerPath);
26 | });
27 | };
28 |
--------------------------------------------------------------------------------
/server/boot/extend-models.js:
--------------------------------------------------------------------------------
1 | module.exports = function(app) {
2 | var remotes = app.remotes(),
3 | StorageService = require('loopback-component-storage').StorageService,
4 | providers = null;
5 |
6 | try {
7 | providers = require('../providers-private.json');
8 | } catch(err) {
9 | providers = require('../providers.json');
10 | }
11 |
12 | var storageHandler = new StorageService({
13 | provider: 'amazon',
14 | key: providers.amazon.key,
15 | keyId: providers.amazon.keyId,
16 | });
17 |
18 | remotes.after('report.listReports', function(ctx, next) {
19 | app.models.httpRequestsInteresting.listInteresting(ctx.args.by, function(err, data) {
20 | if (err) {
21 | throw err;
22 | }
23 | Object.keys(data).forEach(function(key) {
24 | ctx.result[key]["interesting"] = data[key]["count"];
25 | });
26 | next();
27 | });
28 | });
29 |
30 | remotes.after('report.findReports', function(ctx, next) {
31 | app.models.httpRequestsInteresting.findInteresting(ctx.args.country_code, undefined,
32 | ["report_id"], undefined,
33 | function(err, data) {
34 | ctx.result.reports.forEach(function(report, idx) {
35 | if (data[report.report_id]) {
36 | ctx.result.reports[idx]["interesting"] = data[report.report_id].length;
37 | }
38 | });
39 | next();
40 | });
41 | });
42 |
43 | app.get("/reportFiles/:year/:report_filename", function(req, res) {
44 | var container = "ooni-public",
45 | filename = "reports-sanitised/yaml/" + req.params.year + "/" + req.params.report_filename;
46 | storageHandler.download(container, filename, res, function(err, result) {
47 | if (err) {
48 | res.status(500).send(err);
49 | }
50 | });
51 | });
52 | }
53 |
--------------------------------------------------------------------------------
/server/boot/rest-api.js:
--------------------------------------------------------------------------------
1 | module.exports = function mountRestApi(server) {
2 | var restApiRoot = server.get('restApiRoot');
3 | server.use(restApiRoot, server.loopback.rest());
4 | };
5 |
--------------------------------------------------------------------------------
/server/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "host": "0.0.0.0",
3 | "remoting": {}
4 | }
5 |
--------------------------------------------------------------------------------
/server/config.local.js:
--------------------------------------------------------------------------------
1 | var GLOBAL_CONFIG = require('../global-config');
2 |
3 | var isDevEnv = (process.env.NODE_ENV || 'development') === 'development';
4 |
5 | module.exports = {
6 | hostname: GLOBAL_CONFIG.hostname,
7 | restApiRoot: GLOBAL_CONFIG.restApiRoot,
8 | livereload: process.env.LIVE_RELOAD,
9 | isDevEnv: isDevEnv,
10 | indexFile: require.resolve(isDevEnv ?
11 | '../client/ngapp/index.html' : '../client/dist/index.html'),
12 | port: GLOBAL_CONFIG.port,
13 | legacyExplorer: GLOBAL_CONFIG.legacyExplorer,
14 | remoting: {
15 | errorHandler: {
16 | handler: function(error, req, res, next) {
17 | if (error instanceof Error) {
18 | console.log(
19 | 'Error in %s %s: errorName=%s errorMessage=%s \n errorStack=%s',
20 | req.method, req.url, error.name, error.message, error.stack);
21 | }
22 | else {
23 | console.log(req.method, req.originalUrl, res.statusCode, error);
24 | }
25 | next();
26 | }
27 | }
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/server/config.production.json:
--------------------------------------------------------------------------------
1 | {
2 | "host": "0.0.0.0",
3 | "port": 3000
4 | }
5 |
--------------------------------------------------------------------------------
/server/datasources.development.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "db": {
3 | "host": process.env.DB_HOST || "localhost",
4 | "port": process.env.DB_PORT || "5432",
5 | "database": process.env.DB_NAME || "ooni_pipeline",
6 | "username": process.env.DB_USERNAME || "ooni",
7 | "password": process.env.DB_PASSWORD || "",
8 | "name": "postgres",
9 | "debug": false,
10 | "connector": "postgresql",
11 | "ssl": process.env.DISABLE_SSL !== 'true'
12 | },
13 | "mem": {
14 | "name": "mem",
15 | "connector": "memory"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/server/datasources.json:
--------------------------------------------------------------------------------
1 | {
2 | "db": {
3 | "name": "db",
4 | "connector": "memory"
5 | },
6 | "mem": {
7 | "name": "mem",
8 | "connector": "memory"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/server/datasources.production.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "db": {
3 | "host": process.env.DB_HOST || "localhost",
4 | "port": process.env.DB_PORT || "5432",
5 | "database": process.env.DB_NAME || "ooni_pipeline",
6 | "username": process.env.DB_USERNAME || "ooni",
7 | "password": process.env.DB_PASSWORD || "",
8 | "name": "postgres",
9 | "debug": false,
10 | "connector": "postgresql",
11 | "ssl": true
12 | },
13 | "mem": {
14 | "name": "mem",
15 | "connector": "memory"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/server/middleware.json:
--------------------------------------------------------------------------------
1 | {
2 | "initial:before": {
3 | "loopback#favicon": {
4 | "params": "$!../client/ngapp/favicon.ico"
5 | }
6 | },
7 | "initial": {
8 | "compression": {},
9 | "cors": {
10 | "params": {
11 | "origin": true,
12 | "credentials": true,
13 | "maxAge": 86400
14 | }
15 | }
16 | },
17 | "session": {},
18 | "auth": {},
19 | "parse": {},
20 | "routes": {},
21 | "files": {},
22 | "final": {
23 | "loopback#urlNotFound": {}
24 | },
25 | "final:after": {
26 | "errorhandler": {}
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/server/model-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "sources": [
4 | "loopback/common/models",
5 | "loopback/server/models",
6 | "../common/models",
7 | "./models"
8 | ],
9 | "mixins": [
10 | "loopback/common/mixins",
11 | "loopback/server/mixins",
12 | "../common/mixins",
13 | "./mixins"
14 | ]
15 | },
16 | "User": {
17 | "dataSource": "mem",
18 | "public": false
19 | },
20 | "AccessToken": {
21 | "dataSource": "mem",
22 | "public": false
23 | },
24 | "ACL": {
25 | "dataSource": "mem",
26 | "public": false
27 | },
28 | "RoleMapping": {
29 | "dataSource": "mem",
30 | "public": false
31 | },
32 | "Role": {
33 | "dataSource": "mem",
34 | "public": false
35 | },
36 | "report": {
37 | "dataSource": "db",
38 | "public": true
39 | },
40 | "country": {
41 | "dataSource": "db",
42 | "public": true
43 | },
44 | "nettest": {
45 | "dataSource": "db",
46 | "public": true
47 | },
48 | "censorship_method": {
49 | "dataSource": "db",
50 | "public": true
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/server/models/country.js:
--------------------------------------------------------------------------------
1 | module.exports = function(Country) {
2 | }
3 |
--------------------------------------------------------------------------------
/server/models/report.js:
--------------------------------------------------------------------------------
1 | module.exports = function(Report) {
2 | };
3 |
--------------------------------------------------------------------------------
/server/providers.json:
--------------------------------------------------------------------------------
1 | {
2 | "amazon": {
3 | "key": "DzyYAOiWpDwqzEUk4OpuZuOKBDpepk1ff2y863Zv",
4 | "keyId": "AKIAICLIHHE7CXMFVGDA"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var loopback = require('loopback');
3 | var boot = require('loopback-boot');
4 |
5 | var app = module.exports = loopback();
6 |
7 | // middleware
8 | app.use(loopback.compress());
9 |
10 | // it's important to register the livereload middleware
11 | // after any response-processing middleware like compress,
12 | // but before any middleware serving actual content
13 | var livereload = app.get('livereload');
14 | if (livereload) {
15 | app.use(require('connect-livereload')({
16 | port: livereload
17 | }));
18 | }
19 |
20 | // boot scripts mount components like REST API
21 | boot(app, __dirname);
22 |
23 | // Mount static files like ngapp
24 | // All static middleware should be registered at the end, as all requests
25 | // passing the static middleware are hitting the file system
26 | app.use(loopback.static(path.dirname(app.get('indexFile'))));
27 |
28 | // Requests that get this far won't be handled
29 | // by any middleware. Convert them into a 404 error
30 | // that will be handled later down the chain.
31 | app.use(loopback.urlNotFound());
32 |
33 | // The ultimate error handler.
34 | app.use(loopback.errorHandler());
35 |
36 | // optionally start the app
37 | app.start = function() {
38 | // start the web server
39 | return app.listen(function() {
40 | app.emit('started');
41 | console.log('Web server listening at: %s', app.get('url'));
42 | });
43 | };
44 |
45 | if (require.main === module) {
46 | app.start();
47 | }
48 |
--------------------------------------------------------------------------------