diff --git a/api/permissions.py b/api/permissions.py
deleted file mode 100644
index 4de11b4f9..000000000
--- a/api/permissions.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from rest_framework import permissions
-from django.conf import settings
-
-
-class WhiteListPermission(permissions.BasePermission):
- """
- This class is used in our Django Rest Framework to check
- that the incoming request is from a whitelisted IP.
-
- In practice, it is used to only allow requests to our backend API
- to come directly from an api.data.gov proxy.
- """
-
- def has_permission(self, request, view):
- if not settings.REST_FRAMEWORK['WHITELIST']:
- # if no WHITELIST, then permission is allowed
- return True
-
- forwarded = request.META.get('HTTP_X_FORWARDED_FOR')
- if forwarded:
- ip_addresses = [f.strip() for f in forwarded.split(',')]
- else:
- ip_addresses = [request.META['REMOTE_ADDR']]
-
- for ip in ip_addresses:
- if ip in settings.REST_FRAMEWORK['WHITELIST']:
- return True
-
- return False
diff --git a/api/tests/test_permissions.py b/api/tests/test_permissions.py
deleted file mode 100644
index acf0b71b1..000000000
--- a/api/tests/test_permissions.py
+++ /dev/null
@@ -1,64 +0,0 @@
-from unittest import mock
-from django.test import SimpleTestCase, override_settings
-
-from ..permissions import WhiteListPermission
-
-
-@override_settings(REST_FRAMEWORK={'WHITELIST': ['1.1.2.2']})
-class WhiteListPermissionTests(SimpleTestCase):
- @override_settings(REST_FRAMEWORK={'WHITELIST': None})
- def test_it_returns_true_when_no_whitelist_setting(self):
- w = WhiteListPermission()
- self.assertTrue(w.has_permission(None, None))
-
- def test_it_returns_true_when_forwarded_for_ip_is_whitelisted(self):
- w = WhiteListPermission()
- req = mock.MagicMock(
- META={'HTTP_X_FORWARDED_FOR': '5.5.5.5, 1.1.2.2, 2.2.3.3'}
- )
- self.assertTrue(w.has_permission(req, None))
- req = mock.MagicMock(
- META={'HTTP_X_FORWARDED_FOR': ' 1.1.2.2 '}
- )
- self.assertTrue(w.has_permission(req, None))
-
- def test_it_returns_false_when_forwarded_for_ip_is_not_whitelisted(self):
- w = WhiteListPermission()
- req = mock.MagicMock(
- META={'HTTP_X_FORWARDED_FOR': '5.5.5.5, 2.2.3.3'}
- )
- self.assertFalse(w.has_permission(req, None))
-
- def test_it_returns_true_when_remote_addr_is_whitelisted(self):
- w = WhiteListPermission()
- req = mock.MagicMock(
- META={'REMOTE_ADDR': '1.1.2.2'}
- )
- self.assertTrue(w.has_permission(req, None))
-
- def test_it_returns_false_when_remote_addr_is_not_whitelisted(self):
- w = WhiteListPermission()
- req = mock.MagicMock(
- META={'REMOTE_ADDR': '5.6.7.8'}
- )
- self.assertFalse(w.has_permission(req, None))
-
- def test_it_returns_false_when_neither_header_has_whitelisted_ip(self):
- w = WhiteListPermission()
- req = mock.MagicMock(
- META={
- 'HTTP_X_FORWARDED_FOR': '5.5.5.5, 2.2.3.3',
- 'REMOTE_ADDR': '5.6.7.8'
- }
- )
- self.assertFalse(w.has_permission(req, None))
-
- def test_it_prefers_forwarded_for_to_remote_addr(self):
- w = WhiteListPermission()
- req = mock.MagicMock(
- META={
- 'HTTP_X_FORWARDED_FOR': '5.5.5.5, 2.2.3.3',
- 'REMOTE_ADDR': '1.1.2.2'
- }
- )
- self.assertFalse(w.has_permission(req, None))
diff --git a/data_explorer/templates/base.html b/data_explorer/templates/base.html
index 46551378a..52f194585 100644
--- a/data_explorer/templates/base.html
+++ b/data_explorer/templates/base.html
@@ -33,10 +33,6 @@
-
-
diff --git a/docs/api.md b/docs/api.md
index 8cf4b7de1..68fd14eda 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -2,26 +2,17 @@
CALC's back end exposes a public API for its labor rates data. This API is used by CALC's front end Data Explorer application, and can also be accessed by any third-party application over the public internet.
-# Local development vs. deployed instances
-
-When developing CALC locally, the API is served from the relative URL prefix `/api/` (for example `http://localhost:8000/api/rates`).
-
-In its deployed instances (development, staging, and production), CALC's public API is fronted by an [API Umbrella][] instance on [api.data.gov](https://api.data.gov) which proxies all API requests to CALC. This allows CALC to not have to concern itself with details like rate limiting. The production CALC API is available at `https://api.data.gov/gsa/calc/`.
-
-In order to ensure that API requests work both in local development and in deployed instances, CALC's front end code should not simply make requests against the relative `/api/` URLs. Instead, a global JavaScript variable called `API_HOST` is available for use as a prefix to requests. When developing locally, it will be set to `/api/`, but on CALC's development, staging, and production deployments it will be an absolute URL.
-
-[API Umbrella]: https://apiumbrella.io/
-
## API endpoints
-The following documentation assumes you're trying to access the API from the production instance via a tool like `curl` at `https://api.data.gov/gsa/calc/`. In development, use `http://localhost:8000/api/` or rely on the `API_HOST` variable.
+The following documentation assumes you're trying to access the API from the production instance via a tool like `curl` at `https://calc.gsa.gov/api/`.
+In development, use `http://localhost:8000/api/`.
### `/rates/`
You can access labor rate information at `/rates/`.
```
-https://api.data.gov/gsa/calc/rates/
+https://calc.gsa.gov/api/rates/
```
#### Labor Categories
@@ -29,7 +20,7 @@ https://api.data.gov/gsa/calc/rates/
You can search for prices of specific labor categories by using the `q` parameter. For example:
```
-https://api.data.gov/gsa/calc/rates/?q=accountant
+https://calc.gsa.gov/api/rates/?q=accountant
```
You can change the way that labor categories are searched by using the `query_type` parameter, which can be either:
@@ -41,13 +32,13 @@ You can change the way that labor categories are searched by using the `query_ty
You can search for multiple labor categories separated by a comma.
```
-https://api.data.gov/gsa/calc/rates/?q=trainer,instructor
+https://calc.gsa.gov/api/rates/?q=trainer,instructor
```
If any of the labor categories you'd like included in your search has a comma, you can surround that labor category with quotation marks:
```
-https://api.data.gov/gsa/calc/rates/?q="engineer, senior",instructor
+https://calc.gsa.gov/api/rates/?q="engineer, senior",instructor
```
All of the query types are case-insensitive.
@@ -59,14 +50,14 @@ All of the query types are case-insensitive.
You can also filter by the minimum years of experience and maximum years of experience. For example:
```
-https://api.data.gov/gsa/calc/rates/?&min_experience=5&max_experience=10&q=technical
+https://calc.gsa.gov/api/rates/?&min_experience=5&max_experience=10&q=technical
```
Or, you can filter with a single, comma-separated range.
For example, if you wanted results with more than five years and less than ten years of experience:
```
-https://api.data.gov/gsa/calc/rates/?experience_range=5,10
+https://calc.gsa.gov/api/rates/?experience_range=5,10
```
##### Education
@@ -82,20 +73,20 @@ These filters accept one or more (comma-separated) education values:
* `PHD` (Ph.D).
```
-https://api.data.gov/gsa/calc/rates/?education=AA,BA
+https://calc.gsa.gov/api/rates/?education=AA,BA
```
Use `min_education` to get all results that meet and exceed the selected education.
The following example will return results that have an education level of `MA` or `PHD`:
```
-https://api.data.gov/gsa/calc/rates/?min_education=MA
+https://calc.gsa.gov/api/rates/?min_education=MA
```
The default pagination is set to 200. You can paginate using the `page` parameter:
```
-https://api.data.gov/gsa/calc/rates/?q=translator&page=2
+https://calc.gsa.gov/api/rates/?q=translator&page=2
```
#### Price Filters
@@ -103,15 +94,15 @@ https://api.data.gov/gsa/calc/rates/?q=translator&page=2
You can filter by price with any of the `price` (exact match), `price__lte` (price is less than or equal to) or `price__gte` (price is greater than or equal to) parameters:
```
-https://api.data.gov/gsa/calc/rates/?price=95
-https://api.data.gov/gsa/calc/rates/?price__lte=95
-https://api.data.gov/gsa/calc/rates/?price__gte=95
+https://calc.gsa.gov/api/rates/?price=95
+https://calc.gsa.gov/api/rates/?price__lte=95
+https://calc.gsa.gov/api/rates/?price__gte=95
```
The `price__lte` and `price__gte` parameters may be used together to search for a price range:
```
-https://api.data.gov/gsa/calc/rates/?price__gte=95&price__lte=105
+https://calc.gsa.gov/api/rates/?price__gte=95&price__lte=105
```
#### Excluding Records
@@ -119,7 +110,7 @@ https://api.data.gov/gsa/calc/rates/?price__gte=95&price__lte=105
You can also exclude specific records from the results by passing in an `exclude` parameter and a comma-separated list of ids:
```
-https://api.data.gov/gsa/calc/rates/?q=environmental+technician&exclude=875173,875749
+https://calc.gsa.gov/api/rates/?q=environmental+technician&exclude=875173,875749
```
#### Other Filters
@@ -134,7 +125,7 @@ Other parameters allow you to filter by:
Here is an example with all four parameters (`schedule`, `sin`, `site`, and `business_size`) included:
```
-https://api.data.gov/gsa/calc/rates/?schedule=mobis&sin=874&site=customer&business_size=s
+https://calc.gsa.gov/api/rates/?schedule=mobis&sin=874&site=customer&business_size=s
```
For schedules, there are 8 different values that will return results (case-insensitive):
diff --git a/docs/deploy.md b/docs/deploy.md
index 0628266bc..bdfeaae0c 100644
--- a/docs/deploy.md
+++ b/docs/deploy.md
@@ -249,55 +249,6 @@ cd /home/vcap/app
source /home/vcap/app/.profile.d/python.sh
```
-### Setting up the API
-
-As mentioned in the [API documentation](api.md), CALC's public API
-is actually proxied by api.data.gov.
-
-In order to configure the proxying between api.data.gov and CALC,
-you will need to obtain an administrative account on api.data.gov.
-For more information on doing this, see the [api.data.gov User Manual][].
-
-You'll then want to tell api.data.gov what host it will listen for, and
-what host your API backend is listening on. For example:
-
-
-
-
Frontend Host
-
Backend Host
-
-
-
api.data.gov
-
calc-prod.app.cloud.gov
-
-
-
-You will also want to configure your API backend on
-api.data.gov with one **Matching URL Prefixes** entry.
-The **Backend Prefix** should always be `/api/`, while the
-**Frontend Prefix** is up to you. Here's an example:
-
-
-
-
Frontend Prefix
-
Backend Prefix
-
-
-
/gsa/calc/
-
/api/
-
-
-
-Now you'll need to configure `API_HOST` on your CALC instance to be
-the combination of your **Frontend Host** and **Frontend Prefix**.
-For example, given the earlier examples listed above, your
-`API_HOST` setting on CALC would be `https://api.data.gov/gsa/calc/`.
-
-Finally, as mentioned in the [Securing your API backend][] section of the
-user manual, you will likely need to configure `WHITELISTED_IPS` on
-your CALC instance to ensure that clients can't bypass rate limiting by
-directly contacting your CALC instance.
-
### Testing production deployments
Because reverse proxies like CloudFront can be misconfigured to prevent
diff --git a/docs/environment.md b/docs/environment.md
index ee3c61bb7..5eb84450a 100644
--- a/docs/environment.md
+++ b/docs/environment.md
@@ -80,17 +80,6 @@ string), the boolean is true; otherwise, it's false.
If this is undefined and `DEBUG` is true, then a built-in Fake UAA Provider
will be used to "simulate" cloud.gov login.
-* `WHITELISTED_IPS` is a comma-separated string of IP addresses that specifies
- IPs that the REST API will accept requests from. Any IPs not in the list
- attempting to access the API will receive a 403 Forbidden response.
- Example: `127.0.0.1,192.168.1.1`.
-
-* `API_HOST` is the relative or absolute URL used to access the
- API hosted by CALC. It defaults to `/api/` but may need to be changed
- if the API has a proxy in front of it, as it likely will be if deployed
- on government infrastructure. For more information, see
- the [API documentation](api.md).
-
* `SECURITY_HEADERS_ON_ERROR_ONLY` is a boolean value that indicates whether
security-related response headers (such as `X-XSS-Protection`)
should only be added on error (status code >= 400) responses. This setting
diff --git a/frontend/source/js/data-explorer/api.js b/frontend/source/js/data-explorer/api.js
index e1893b0d2..5618b7d0b 100644
--- a/frontend/source/js/data-explorer/api.js
+++ b/frontend/source/js/data-explorer/api.js
@@ -3,8 +3,8 @@ import xhr from 'xhr';
import * as qs from 'querystring';
export default class API {
- constructor(basePath = '') {
- this.basePath = window.API_HOST || basePath;
+ constructor(basePath = '/api/') {
+ this.basePath = basePath;
if (this.basePath.charAt(this.basePath.length - 1) !== '/') {
this.basePath += '/';
}
diff --git a/frontend/source/js/data-explorer/constants.js b/frontend/source/js/data-explorer/constants.js
index 68837f4f4..0aa0102d9 100644
--- a/frontend/source/js/data-explorer/constants.js
+++ b/frontend/source/js/data-explorer/constants.js
@@ -125,6 +125,4 @@ export const QUERY_TYPE_LABELS = {
export const MAX_QUERY_LENGTH = 255;
-export const API_HOST = window.API_HOST;
-
-export const API_RATES_CSV = `${API_HOST}rates/csv/`;
+export const API_RATES_CSV = '/rates/csv/';
diff --git a/frontend/source/js/data-explorer/tests/api.test.js b/frontend/source/js/data-explorer/tests/api.test.js
index 3217ce754..48f6cca8b 100644
--- a/frontend/source/js/data-explorer/tests/api.test.js
+++ b/frontend/source/js/data-explorer/tests/api.test.js
@@ -18,12 +18,6 @@ describe('API constructor', () => {
const api2 = new API('/api2/');
expect(api2.basePath).toMatch('/api2/');
});
-
- it('uses window.API_HOST if defined', () => {
- window.API_HOST = 'whatever';
- const api = new API();
- expect(api.basePath).toMatch('whatever/');
- });
});
describe('API get', () => {
diff --git a/frontend/templates/tests.html b/frontend/templates/tests.html
index 7f3fac1bf..4a2bf727c 100644
--- a/frontend/templates/tests.html
+++ b/frontend/templates/tests.html
@@ -9,9 +9,6 @@
-
diff --git a/hourglass/context_processors.py b/hourglass/context_processors.py
index 140f82fa9..d26b912d8 100644
--- a/hourglass/context_processors.py
+++ b/hourglass/context_processors.py
@@ -9,11 +9,6 @@ def canonical_url(request):
return {'canonical_url': get_canonical_url(request)}
-def api_host(request):
- '''Include API_HOST in all request contexts'''
- return {'API_HOST': settings.API_HOST}
-
-
def show_debug_ui(request):
'''Include show_debug_ui in all request contexts'''
return {'show_debug_ui': settings.DEBUG and not settings.HIDE_DEBUG_UI}
diff --git a/hourglass/settings.py b/hourglass/settings.py
index 3d52a3884..aee59d0a4 100644
--- a/hourglass/settings.py
+++ b/hourglass/settings.py
@@ -17,7 +17,7 @@
from .settings_utils import (load_cups_from_vcap_services,
load_redis_url_from_vcap_services,
- get_whitelisted_ips, is_running_tests)
+ is_running_tests)
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
@@ -73,8 +73,6 @@
SERVER_EMAIL = os.environ['SERVER_EMAIL']
HELP_EMAIL = os.environ.get('HELP_EMAIL', DEFAULT_FROM_EMAIL)
-API_HOST = os.environ.get('API_HOST', '/api/')
-
GA_TRACKING_ID = os.environ.get('GA_TRACKING_ID', '')
NON_PROD_INSTANCE_NAME = os.environ.get('NON_PROD_INSTANCE_NAME', '')
@@ -88,7 +86,6 @@
'OPTIONS': {
'context_processors': [
'hourglass.context_processors.canonical_url',
- 'hourglass.context_processors.api_host',
'hourglass.context_processors.show_debug_ui',
'hourglass.context_processors.google_analytics_tracking_id',
'hourglass.context_processors.help_email',
@@ -230,10 +227,6 @@
REST_FRAMEWORK = {
'COERCE_DECIMAL_TO_STRING': False,
- 'WHITELIST': get_whitelisted_ips(),
- 'DEFAULT_PERMISSION_CLASSES': (
- 'api.permissions.WhiteListPermission',
- ),
}
LOGGING: Dict[str, Any] = {
diff --git a/hourglass/settings_utils.py b/hourglass/settings_utils.py
index 0bcba7e21..40691d594 100644
--- a/hourglass/settings_utils.py
+++ b/hourglass/settings_utils.py
@@ -1,7 +1,7 @@
import os
import sys
import json
-from typing import List, Optional
+from typing import List
Environ = os._Environ
@@ -29,18 +29,6 @@ def load_cups_from_vcap_services(name: str='calc-env',
env[key] = value
-def get_whitelisted_ips(env: Environ=os.environ) -> Optional[List[str]]:
- '''
- Detects if WHITELISTED_IPS is in the environment; if not,
- returns None. if so, parses WHITELISTED_IPS as a comma-separated
- string and returns a list of values.
- '''
- if 'WHITELISTED_IPS' not in env:
- return None
-
- return [s.strip() for s in env['WHITELISTED_IPS'].split(',')]
-
-
def load_redis_url_from_vcap_services(name: str,
env: Environ=os.environ) -> None:
'''
diff --git a/hourglass/tests/tests.py b/hourglass/tests/tests.py
index 81ec73beb..c5ae0ef47 100644
--- a/hourglass/tests/tests.py
+++ b/hourglass/tests/tests.py
@@ -13,7 +13,6 @@
from .. import healthcheck, __version__
from ..settings_utils import (load_cups_from_vcap_services,
load_redis_url_from_vcap_services,
- get_whitelisted_ips,
is_running_tests)
@@ -236,20 +235,6 @@ def test_redis_url_is_loaded(self):
'redis://:the_password@the_host:1234')
-class GetWhitelistedIPsTest(unittest.TestCase):
-
- def test_returns_none_when_not_in_env(self):
- env = {}
- self.assertIsNone(get_whitelisted_ips(env))
-
- def test_returns_whitelisted_ips_list(self):
- env = {
- 'WHITELISTED_IPS': '1.2.3.4,1.2.3.8, 1.2.3.16'
- }
- ips = get_whitelisted_ips(env)
- self.assertListEqual(ips, ['1.2.3.4', '1.2.3.8', '1.2.3.16'])
-
-
@override_settings(
# This will make tests run faster.
PASSWORD_HASHERS=['django.contrib.auth.hashers.MD5PasswordHasher'],
diff --git a/production_tests/tests.py b/production_tests/tests.py
index 5cc29937e..32fdde7f2 100644
--- a/production_tests/tests.py
+++ b/production_tests/tests.py
@@ -1,22 +1,9 @@
-import re
from urllib.parse import urlparse, parse_qs
from .util import ProductionTestCase
class ProductionTests(ProductionTestCase):
- api_url = None
-
- def get_api_url(self):
- if self.api_url:
- return self.api_url
- res = self.client.get('/')
- m = re.search(r'var API_HOST = "([^"]+)"',
- res.content.decode('utf-8'))
- api_url = m.group(1)
- self.api_url = api_url
- return api_url
-
def test_oauth2_redirect_uri_has_correct_domain(self):
'''
Mitigation against https://github.com/18F/calc/pull/1187.
@@ -93,9 +80,8 @@ def test_api_supports_cors(self):
'''
Mitigation against https://github.com/18F/calc/issues/1307.
'''
- api_url = self.get_api_url()
res = self.client.get(
- api_url + 'search/?format=json&q=zzzzzzzz&query_type=match_all',
+ '/api/search/?format=json&q=zzzzzzzz&query_type=match_all',
headers={'Origin': self.ORIGIN}
)
self.assertEqual(res.status_code, 200)
@@ -103,9 +89,8 @@ def test_api_supports_cors(self):
self.assertEqual(res.headers['Access-Control-Allow-Origin'], '*')
def test_api_passes_json_accept_header(self):
- api_url = self.get_api_url()
res = self.client.get(
- api_url + 'search/?q=zzzzzzzz&query_type=match_all',
+ '/api/search/?q=zzzzzzzz&query_type=match_all',
headers={'Accept': 'application/json'}
)
self.assertEqual(res.status_code, 200)