Better 404 (and 403) pages

Fixes #1819
This commit is contained in:
Joseph Schorr 2016-09-21 13:53:09 -04:00
parent 502fa23d31
commit 4d5c65e6d4
12 changed files with 131 additions and 24 deletions

View file

@ -17,7 +17,7 @@ from auth import scopes
from auth.auth import require_session_login, process_oauth, has_basic_auth, process_auth_or_cookie from auth.auth import require_session_login, process_oauth, has_basic_auth, process_auth_or_cookie
from auth.permissions import (AdministerOrganizationPermission, ReadRepositoryPermission, from auth.permissions import (AdministerOrganizationPermission, ReadRepositoryPermission,
SuperUserPermission, AdministerRepositoryPermission, SuperUserPermission, AdministerRepositoryPermission,
ModifyRepositoryPermission) ModifyRepositoryPermission, OrganizationMemberPermission)
from auth.auth_context import get_authenticated_user from auth.auth_context import get_authenticated_user
from buildtrigger.basehandler import BuildTriggerHandler from buildtrigger.basehandler import BuildTriggerHandler
from buildtrigger.bitbuckethandler import BitbucketBuildTrigger from buildtrigger.bitbuckethandler import BitbucketBuildTrigger
@ -69,7 +69,7 @@ def internal_error_display():
@web.errorhandler(404) @web.errorhandler(404)
@web.route('/404', methods=['GET']) @web.route('/404', methods=['GET'])
def not_found_error_display(e = None): def not_found_error_display(e = None):
resp = render_page_template_with_routedata('404.html') resp = index('', error_code=404)
resp.status_code = 404 resp.status_code = 404
return resp return resp
@ -670,17 +670,46 @@ def attach_custom_build_trigger(namespace_name, repo_name):
@parse_repository_name(include_tag=True) @parse_repository_name(include_tag=True)
@anon_protect @anon_protect
def redirect_to_repository(namespace_name, repo_name, tag_name): def redirect_to_repository(namespace_name, repo_name, tag_name):
permission = ReadRepositoryPermission(namespace_name, repo_name) # Always return 200 for ac-discovery, to ensure that rkt and other ACI-compliant clients can
is_public = model.repository.repository_is_public(namespace_name, repo_name) # find the metadata they need. Permissions will be checked in the registry API.
if request.args.get('ac-discovery', 0) == 1: if request.args.get('ac-discovery', 0) == 1:
return index('') return index('')
if permission.can() or is_public: # Redirect to the repository page if the user can see the repository.
is_public = model.repository.repository_is_public(namespace_name, repo_name)
permission = ReadRepositoryPermission(namespace_name, repo_name)
repo_exists = bool(model.repository.get_repository(namespace_name, repo_name))
if repo_exists and (permission.can() or is_public):
repo_path = '/'.join([namespace_name, repo_name]) repo_path = '/'.join([namespace_name, repo_name])
return redirect(url_for('web.repository', path=repo_path, tab="tags", tag=tag_name)) return redirect(url_for('web.repository', path=repo_path, tab="tags", tag=tag_name))
abort(404) namespace_exists = bool(model.user.get_user_or_org(namespace_name))
namespace_permission = OrganizationMemberPermission(namespace_name).can()
if get_authenticated_user() and get_authenticated_user().username == namespace_name:
namespace_permission = True
# Otherwise, we display an error for the user. Which error we display depends on permissions:
# > If the namespace doesn't exist, 404.
# > If the user is a member of the namespace:
# - If the repository doesn't exist, 404
# - If the repository does exist (no access), 403
# > If the user is not a member of the namespace: 403
error_info = {
'for_repo': True,
'namespace_exists': namespace_exists,
'namespace': namespace_name,
'repo_name': repo_name,
}
if not namespace_exists or (namespace_permission and not repo_exists):
resp = index('', error_code=404, error_info=json.dumps(error_info))
resp.status_code = 404
return resp
else:
resp = index('', error_code=403, error_info=json.dumps(error_info))
resp.status_code = 403
return resp
@web.route('/<namespace>') @web.route('/<namespace>')

View file

@ -0,0 +1,25 @@
.error-view-element {
text-align: center;
}
.error-view-element h2 {
font-size: 42px;
margin-bottom: 10px;
}
.error-view-element h3 {
font-size: 24px;
}
.error-view-element img {
margin-top: 20px;
width: 255px;
height: 235px;
margin-bottom: 40px;
}
.error-view-element .err403 img {
margin-top: 30px;
width: 225px;
height: 205px;
}

View file

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 203.92 187.45"><defs><style>.cls-1{fill:#05ce7c;}.cls-2{fill:#003764;}</style></defs><title>Artboard 1</title><polygon class="cls-1" points="159.61 0 203.92 93.72 159.61 187.45 121.89 187.45 166.2 93.72 121.89 0 159.61 0"/><polygon class="cls-2" points="121.9 187.45 77.59 93.72 121.9 0 159.62 0 115.31 93.72 159.62 187.45 121.9 187.45"/><polygon class="cls-1" points="101.96 42.17 82.02 0 44.3 0 83.1 82.06 101.96 42.17"/><polygon class="cls-1" points="83.1 105.38 44.3 187.45 82.02 187.45 101.96 145.27 83.1 105.38"/><polygon class="cls-2" points="44.31 187.45 0 93.72 44.31 0 82.03 0 37.72 93.72 82.03 187.45 44.31 187.45"/></svg>

After

Width:  |  Height:  |  Size: 719 B

View file

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 204 187.48"><defs><style>.cls-1{fill:#40b4e5;}.cls-2{fill:#003764;}</style></defs><title>Artboard 1</title><polygon class="cls-1" points="159.68 -0.04 204 93.72 159.68 187.48 121.94 187.48 166.27 93.72 121.94 -0.04 159.68 -0.04"/><polygon class="cls-2" points="121.94 187.48 77.62 93.72 121.94 -0.04 159.68 -0.04 115.35 93.72 159.68 187.48 121.94 187.48"/><polygon class="cls-1" points="102 42.15 82.06 -0.04 44.32 -0.04 83.13 82.06 102 42.15"/><polygon class="cls-1" points="83.13 105.39 44.32 187.48 82.06 187.48 102 145.29 83.13 105.39"/><polygon class="cls-2" points="44.33 187.48 0 93.72 44.33 -0.04 82.06 -0.04 37.73 93.72 82.06 187.48 44.33 187.48"/></svg>

After

Width:  |  Height:  |  Size: 749 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.7 KiB

View file

@ -207,8 +207,9 @@ quayApp.config(['$routeProvider', '$locationProvider', 'pages', function($routeP
// Public Repo Experiments // Public Repo Experiments
.route('/__exp/publicRepo', 'public-repo-exp') .route('/__exp/publicRepo', 'public-repo-exp')
// Default: Redirect to the landing page // 404/403
.otherwise({redirectTo: '/'}); .route('/:catchall', 'error-view')
.route('/:catch/:all', 'error-view');
}]); }]);
// Configure compile provider to add additional URL prefixes to the sanitization list. We use // Configure compile provider to add additional URL prefixes to the sanitization list. We use

View file

@ -0,0 +1,17 @@
(function() {
/**
* Error view page.
*/
angular.module('quayPages').config(['pages', function(pages) {
pages.create('error-view', 'error-view.html', ErrorViewCtrl, {
'title': '{{code}}',
'description': 'Error',
'newLayout': false
});
}]);
function ErrorViewCtrl($scope, ApiService, $routeParams, UserService) {
$scope.info = window.__error_info;
$scope.code = window.__error_code;
}
}());

View file

@ -13,6 +13,9 @@
function NewOrgCtrl($scope, $routeParams, $timeout, $location, UserService, PlanService, ApiService, CookieService, Features) { function NewOrgCtrl($scope, $routeParams, $timeout, $location, UserService, PlanService, ApiService, CookieService, Features) {
$scope.Features = Features; $scope.Features = Features;
$scope.holder = {}; $scope.holder = {};
$scope.org = {
'name': $routeParams['namespace'] || ''
};
UserService.updateUserIn($scope); UserService.updateUserIn($scope);

View file

@ -10,7 +10,7 @@
}) })
}]); }]);
function NewRepoCtrl($scope, $location, $http, $timeout, UserService, ApiService, PlanService, TriggerService, Features) { function NewRepoCtrl($scope, $location, $http, $timeout, $routeParams, UserService, ApiService, PlanService, TriggerService, Features) {
UserService.updateUserIn($scope); UserService.updateUserIn($scope);
$scope.Features = Features; $scope.Features = Features;
@ -19,7 +19,8 @@
$scope.repo = { $scope.repo = {
'is_public': 0, 'is_public': 0,
'description': '', 'description': '',
'initialize': '' 'initialize': '',
'name': $routeParams['name']
}; };
$scope.changeNamespace = function(namespace) { $scope.changeNamespace = function(namespace) {

View file

@ -0,0 +1,33 @@
<div class="error-view-element">
<!-- 404 -->
<div class="err404" ng-if="code == 404">
<h2>404: Not Found</h2>
<h3 ng-if="!info.for_repo && !info.namespace_exists">The resource you're looking for doesn't exists</h3>
<h3 ng-if="info && !info.namespace_exists">Namespace <strong>{{ info.namespace }}</strong> doesn't exists</h3>
<h3 ng-if="info && info.for_repo && info.namespace_exists">The repository you're looking for doesn't exists</h3>
<img src="/static/img/40x/quay-logo-404.svg">
<h4 ng-if="!info.for_repo && !info.namespace_exists">
Return to the <a href="/">main page</a>
</h4>
<h4 ng-if="info && !info.namespace_exists">
<a href="/organizations/new?namespace={{ info.namespace }}">Create this namespace</a> or return to the <a href="/">main page</a>
</h4>
<h4 ng-if="info && info.for_repo && info.namespace_exists">
<a href="/new?namespace={{ info.namespace }}&name={{ info.repo_name }}">Create this repository</a> or return to the <a href="/">main page</a>
</h4>
</div>
<!-- 403 -->
<div class="err403" ng-if="code == 403">
<h2>403: Unauthorized</h2>
<h3 ng-if="!info.for_repo">You are not authorized to view this resource</h3>
<h3 ng-if="info.for_repo">You are not authorized to view this repository</h3>
<img src="/static/img/40x/QE-logomark.svg" quay-show="!Features.BILLING">
<img src="/static/img/40x/Quay-logomark.svg" quay-show="Features.BILLING">
<h4 ng-if="info.for_repo">Contact the admin of the <strong>{{ info.namespace }}</strong> namespace for access to the repository or you can return to the <a href="/">main page</a></h4>
<h4 ng-if="!info.for_repo">Return to the <a href="/">main page</a></h4>
</div>
</div>

View file

@ -1,13 +0,0 @@
{% extends "error.html" %}
{% block title %}
<title>Page Not Found · Quay</title>
{% endblock %}
{% block content %}
<h3>The page you're looking for doesn't exist!</h3>
<h4>
<p>This is somewhat embarrassing, isnt it? It looks like there's nothing here.</p>
<p>You probably want to return to the <a href="/">main page</a>.</p>
</h4>
{% endblock %}

View file

@ -40,6 +40,14 @@
window.__auth_scopes = {{ scope_set|safe }}; window.__auth_scopes = {{ scope_set|safe }};
window.__vuln_priority = {{ vuln_priority_set|safe }} window.__vuln_priority = {{ vuln_priority_set|safe }}
window.__token = '{{ csrf_token() }}'; window.__token = '{{ csrf_token() }}';
{% if error_code %}
window.__error_code = {{ error_code }};
{% endif %}
{% if error_info %}
window.__error_info = {{ error_info|safe }};
{% endif %}
</script> </script>
{% for script_url in external_scripts %} {% for script_url in external_scripts %}