Fix OAuth redirect for denial action when generating for internal tokens
This commit is contained in:
parent
dd28a845db
commit
5516911de9
5 changed files with 56 additions and 16 deletions
|
@ -19,7 +19,7 @@ def build_requests_session():
|
||||||
CLIENT_WHITELIST = ['SERVER_HOSTNAME', 'PREFERRED_URL_SCHEME', 'MIXPANEL_KEY',
|
CLIENT_WHITELIST = ['SERVER_HOSTNAME', 'PREFERRED_URL_SCHEME', 'MIXPANEL_KEY',
|
||||||
'STRIPE_PUBLISHABLE_KEY', 'ENTERPRISE_LOGO_URL', 'SENTRY_PUBLIC_DSN',
|
'STRIPE_PUBLISHABLE_KEY', 'ENTERPRISE_LOGO_URL', 'SENTRY_PUBLIC_DSN',
|
||||||
'AUTHENTICATION_TYPE', 'REGISTRY_TITLE', 'REGISTRY_TITLE_SHORT',
|
'AUTHENTICATION_TYPE', 'REGISTRY_TITLE', 'REGISTRY_TITLE_SHORT',
|
||||||
'CONTACT_INFO', 'AVATAR_KIND']
|
'CONTACT_INFO', 'AVATAR_KIND', 'LOCAL_OAUTH_HANDLER']
|
||||||
|
|
||||||
|
|
||||||
def getFrontendVisibleConfig(config_dict):
|
def getFrontendVisibleConfig(config_dict):
|
||||||
|
@ -213,6 +213,9 @@ class DefaultConfig(object):
|
||||||
# Signed registry grant token expiration in seconds
|
# Signed registry grant token expiration in seconds
|
||||||
SIGNED_GRANT_EXPIRATION_SEC = 60 * 60 * 24 # One day to complete a push/pull
|
SIGNED_GRANT_EXPIRATION_SEC = 60 * 60 * 24 # One day to complete a push/pull
|
||||||
|
|
||||||
|
# The URL endpoint to which we redirect OAuth when generating a token locally.
|
||||||
|
LOCAL_OAUTH_HANDLER = '/oauth/localapp'
|
||||||
|
|
||||||
# The various avatar background colors.
|
# The various avatar background colors.
|
||||||
AVATAR_KIND = 'local'
|
AVATAR_KIND = 'local'
|
||||||
AVATAR_COLORS = ['#969696', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728',
|
AVATAR_COLORS = ['#969696', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728',
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from flask import url_for
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from oauth2lib.provider import AuthorizationProvider
|
from oauth2lib.provider import AuthorizationProvider
|
||||||
from oauth2lib import utils
|
from oauth2lib import utils
|
||||||
|
@ -9,12 +10,10 @@ from data.database import (OAuthApplication, OAuthAuthorizationCode, OAuthAccess
|
||||||
random_string_generator)
|
random_string_generator)
|
||||||
from data.model.legacy import get_user
|
from data.model.legacy import get_user
|
||||||
from auth import scopes
|
from auth import scopes
|
||||||
from flask import render_template
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DatabaseAuthorizationProvider(AuthorizationProvider):
|
class DatabaseAuthorizationProvider(AuthorizationProvider):
|
||||||
def get_authorized_user(self):
|
def get_authorized_user(self):
|
||||||
raise NotImplementedError('Subclasses must fill in the ability to get the authorized_user.')
|
raise NotImplementedError('Subclasses must fill in the ability to get the authorized_user.')
|
||||||
|
@ -45,9 +44,12 @@ class DatabaseAuthorizationProvider(AuthorizationProvider):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def validate_redirect_uri(self, client_id, redirect_uri):
|
def validate_redirect_uri(self, client_id, redirect_uri):
|
||||||
|
if redirect_uri == url_for('web.oauth_local_handler', _external=True):
|
||||||
|
return True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
app = OAuthApplication.get(client_id=client_id)
|
oauth_app = OAuthApplication.get(client_id=client_id)
|
||||||
if app.redirect_uri and redirect_uri and redirect_uri.startswith(app.redirect_uri):
|
if oauth_app.redirect_uri and redirect_uri and redirect_uri.startswith(oauth_app.redirect_uri):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
except OAuthApplication.DoesNotExist:
|
except OAuthApplication.DoesNotExist:
|
||||||
|
@ -106,9 +108,9 @@ class DatabaseAuthorizationProvider(AuthorizationProvider):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def persist_authorization_code(self, client_id, code, scope):
|
def persist_authorization_code(self, client_id, code, scope):
|
||||||
app = OAuthApplication.get(client_id=client_id)
|
oauth_app = OAuthApplication.get(client_id=client_id)
|
||||||
data = self._generate_data_string()
|
data = self._generate_data_string()
|
||||||
OAuthAuthorizationCode.create(application=app, code=code, scope=scope, data=data)
|
OAuthAuthorizationCode.create(application=oauth_app, code=code, scope=scope, data=data)
|
||||||
|
|
||||||
def persist_token_information(self, client_id, scope, access_token, token_type, expires_in,
|
def persist_token_information(self, client_id, scope, access_token, token_type, expires_in,
|
||||||
refresh_token, data):
|
refresh_token, data):
|
||||||
|
@ -116,9 +118,9 @@ class DatabaseAuthorizationProvider(AuthorizationProvider):
|
||||||
if not user:
|
if not user:
|
||||||
raise RuntimeError('Username must be in the data field')
|
raise RuntimeError('Username must be in the data field')
|
||||||
|
|
||||||
app = OAuthApplication.get(client_id=client_id)
|
oauth_app = OAuthApplication.get(client_id=client_id)
|
||||||
expires_at = datetime.utcnow() + timedelta(seconds=expires_in)
|
expires_at = datetime.utcnow() + timedelta(seconds=expires_in)
|
||||||
OAuthAccessToken.create(application=app, authorized_user=user, scope=scope,
|
OAuthAccessToken.create(application=oauth_app, authorized_user=user, scope=scope,
|
||||||
access_token=access_token, token_type=token_type,
|
access_token=access_token, token_type=token_type,
|
||||||
expires_at=expires_at, refresh_token=refresh_token, data=data)
|
expires_at=expires_at, refresh_token=refresh_token, data=data)
|
||||||
|
|
||||||
|
@ -163,7 +165,7 @@ class DatabaseAuthorizationProvider(AuthorizationProvider):
|
||||||
|
|
||||||
# Check redirect URI
|
# Check redirect URI
|
||||||
is_valid_redirect_uri = self.validate_redirect_uri(client_id, redirect_uri)
|
is_valid_redirect_uri = self.validate_redirect_uri(client_id, redirect_uri)
|
||||||
if redirect_uri != 'display' and not is_valid_redirect_uri:
|
if not is_valid_redirect_uri:
|
||||||
return self._invalid_redirect_uri_response()
|
return self._invalid_redirect_uri_response()
|
||||||
|
|
||||||
# Check conditions
|
# Check conditions
|
||||||
|
@ -198,10 +200,6 @@ class DatabaseAuthorizationProvider(AuthorizationProvider):
|
||||||
url = utils.build_url(redirect_uri, params)
|
url = utils.build_url(redirect_uri, params)
|
||||||
url += '#access_token=%s&token_type=%s&expires_in=%s' % (access_token, token_type, expires_in)
|
url += '#access_token=%s&token_type=%s&expires_in=%s' % (access_token, token_type, expires_in)
|
||||||
|
|
||||||
if redirect_uri == 'display':
|
|
||||||
return self._make_response(
|
|
||||||
render_template("message.html", message="Access Token: " + access_token))
|
|
||||||
|
|
||||||
return self._make_response(headers={'Location': url}, status_code=302)
|
return self._make_response(headers={'Location': url}, status_code=302)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -414,6 +414,19 @@ def authorize_application():
|
||||||
return provider.get_token_response('token', client_id, redirect_uri, scope=scope)
|
return provider.get_token_response('token', client_id, redirect_uri, scope=scope)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@web.route(app.config['LOCAL_OAUTH_HANDLER'], methods=['GET'])
|
||||||
|
def oauth_local_handler():
|
||||||
|
if not current_user.is_authenticated():
|
||||||
|
abort(401)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not request.args.get('scope'):
|
||||||
|
return render_page_template("message.html", message="Authorization canceled")
|
||||||
|
else:
|
||||||
|
return render_page_template("generatedtoken.html")
|
||||||
|
|
||||||
|
|
||||||
@web.route('/oauth/denyapp', methods=['POST'])
|
@web.route('/oauth/denyapp', methods=['POST'])
|
||||||
@csrf_protect
|
@csrf_protect
|
||||||
def deny_application():
|
def deny_application():
|
||||||
|
@ -444,7 +457,7 @@ def request_authorization_code():
|
||||||
|
|
||||||
if (not current_user.is_authenticated() or
|
if (not current_user.is_authenticated() or
|
||||||
not provider.validate_has_scopes(client_id, current_user.db_user().username, scope)):
|
not provider.validate_has_scopes(client_id, current_user.db_user().username, scope)):
|
||||||
if redirect_uri != 'display' and not provider.validate_redirect_uri(client_id, redirect_uri):
|
if not provider.validate_redirect_uri(client_id, redirect_uri):
|
||||||
current_app = provider.get_application_for_client_id(client_id)
|
current_app = provider.get_application_for_client_id(client_id)
|
||||||
if not current_app:
|
if not current_app:
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
|
@ -114,7 +114,7 @@
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<a class="btn btn-success"
|
<a class="btn btn-success"
|
||||||
href="{{ Config.getUrl('/oauth/authorize?response_type=token&client_id=' + application.client_id + '&scope=' + getScopes(genScopes).join(',') + '&redirect_uri=display') }}"
|
href="{{ Config.getUrl('/oauth/authorize?response_type=token&client_id=' + application.client_id + '&scope=' + getScopes(genScopes).join(',') + '&redirect_uri=' + Config.getUrl(Config['LOCAL_OAUTH_HANDLER'])) }}"
|
||||||
ng-disabled="!getScopes(genScopes).length" target="_blank">
|
ng-disabled="!getScopes(genScopes).length" target="_blank">
|
||||||
Generate Access Token
|
Generate Access Token
|
||||||
</a>
|
</a>
|
||||||
|
|
26
templates/generatedtoken.html
Normal file
26
templates/generatedtoken.html
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<html>
|
||||||
|
<title>Quay.io</title>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css">
|
||||||
|
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.no-icons.min.css">
|
||||||
|
<link href='//fonts.googleapis.com/css?family=Droid+Sans:400,700' rel='stylesheet' type='text/css'>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container" style="margin-top: 20px">
|
||||||
|
<img src="/static/img/quay-logo.png">
|
||||||
|
<h5>Access Token:
|
||||||
|
<script type="text/javascript">
|
||||||
|
var hash = document.location.hash.substr(1);
|
||||||
|
var pairs = hash.split('&');
|
||||||
|
for (var i = 0; i < pairs.length; ++i) {
|
||||||
|
var pair = pairs[i];
|
||||||
|
var kv = pair.split('=');
|
||||||
|
if (kv[0] == 'access_token') {
|
||||||
|
document.write(kv[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
Reference in a new issue