diff --git a/.gitignore b/.gitignore index e0d723630..00e24caf7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ grunt/node_modules dist dest node_modules +static/ldn +static/fonts +stack_local diff --git a/config.py b/config.py index 1cd28f967..ca1d0ce65 100644 --- a/config.py +++ b/config.py @@ -18,7 +18,7 @@ def build_requests_session(): # values are set to the frontend, DO NOT PLACE ANY SECRETS OR KEYS in this list. CLIENT_WHITELIST = ['SERVER_HOSTNAME', 'PREFERRED_URL_SCHEME', 'GITHUB_CLIENT_ID', 'GITHUB_LOGIN_CLIENT_ID', 'MIXPANEL_KEY', 'STRIPE_PUBLISHABLE_KEY', - 'ENTERPRISE_LOGO_URL', 'SENTRY_PUBLIC_DSN'] + 'ENTERPRISE_LOGO_URL', 'SENTRY_PUBLIC_DSN', 'AUTHENTICATION_TYPE'] def getFrontendVisibleConfig(config_dict): diff --git a/endpoints/api/__init__.py b/endpoints/api/__init__.py index 3abefa54f..d12e18b4f 100644 --- a/endpoints/api/__init__.py +++ b/endpoints/api/__init__.py @@ -66,6 +66,10 @@ class Unauthorized(ApiException): ApiException.__init__(self, 'insufficient_scope', 403, 'Unauthorized', payload) +class ExceedsLicenseException(ApiException): + def __init__(self, payload=None): + ApiException.__init__(self, None, 402, 'Payment Required', payload) + class NotFound(ApiException): def __init__(self, payload=None): @@ -275,6 +279,10 @@ def request_error(exception=None, **kwargs): raise InvalidRequest(message, data) +def license_error(exception=None): + raise ExceedsLicenseException() + + def log_action(kind, user_or_orgname, metadata=None, repo=None): if not metadata: metadata = {} diff --git a/endpoints/api/user.py b/endpoints/api/user.py index 2b663de60..41012e8ab 100644 --- a/endpoints/api/user.py +++ b/endpoints/api/user.py @@ -8,7 +8,7 @@ from flask.ext.principal import identity_changed, AnonymousIdentity from app import app, billing as stripe, authentication from endpoints.api import (ApiResource, nickname, resource, validate_json_request, request_error, log_action, internal_only, NotFound, require_user_admin, - InvalidToken, require_scope, format_date, hide_if, show_if) + InvalidToken, require_scope, format_date, hide_if, show_if, license_error) from endpoints.api.subscribe import subscribe from endpoints.common import common_login from data import model @@ -192,6 +192,8 @@ class User(ApiResource): code = model.create_confirm_email_code(new_user) send_confirmation_email(new_user.username, new_user.email, code.code) return 'Created', 201 + except model.TooManyUsersException as ex: + raise license_error(exception=ex) except model.DataModelException as ex: raise request_error(exception=ex) @@ -226,7 +228,12 @@ def conduct_signin(username_or_email, password): needs_email_verification = False invalid_credentials = False - verified = authentication.verify_user(username_or_email, password) + verified = None + try: + verified = authentication.verify_user(username_or_email, password) + except model.TooManyUsersException as ex: + raise license_error(exception=ex) + if verified: if common_login(verified): return {'success': True} @@ -244,6 +251,7 @@ def conduct_signin(username_or_email, password): @resource('/v1/user/convert') @internal_only +@show_if(app.config['AUTHENTICATION_TYPE'] == 'Database') class ConvertToOrganization(ApiResource): """ Operations for converting a user to an organization. """ schemas = { diff --git a/endpoints/common.py b/endpoints/common.py index bcd80a5ce..e404bde05 100644 --- a/endpoints/common.py +++ b/endpoints/common.py @@ -164,6 +164,7 @@ def render_page_template(name, **kwargs): is_debug=str(app.config.get('DEBUGGING', False)).lower(), show_chat=features.OLARK_CHAT, cache_buster=cache_buster, + has_billing=features.BILLING, **kwargs)) resp.headers['X-FRAME-OPTIONS'] = 'DENY' diff --git a/endpoints/index.py b/endpoints/index.py index 2d134fbc2..ac1930af6 100644 --- a/endpoints/index.py +++ b/endpoints/index.py @@ -112,7 +112,12 @@ def create_user(): else: # New user case profile.debug('Creating user') - new_user = model.create_user(username, password, user_data['email']) + new_user = None + + try: + new_user = model.create_user(username, password, user_data['email']) + except model.TooManyUsersException as ex: + abort(402, 'Seat limit has been reached for this license', issue='seat-limit') profile.debug('Creating email code for user') code = model.create_confirm_email_code(new_user) diff --git a/static/js/app.js b/static/js/app.js index e41c06a74..86f915723 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -4755,8 +4755,8 @@ quayApp.directive('ngVisible', function () { }; }); -quayApp.run(['$location', '$rootScope', 'Restangular', 'UserService', 'PlanService', '$http', '$timeout', 'CookieService', - function($location, $rootScope, Restangular, UserService, PlanService, $http, $timeout, CookieService) { +quayApp.run(['$location', '$rootScope', 'Restangular', 'UserService', 'PlanService', '$http', '$timeout', 'CookieService', 'Features', + function($location, $rootScope, Restangular, UserService, PlanService, $http, $timeout, CookieService, Features) { // Handle session security. Restangular.setDefaultRequestParams(['post', 'put', 'remove', 'delete'], {'_csrf_token': window.__token || ''}); @@ -4770,6 +4770,11 @@ quayApp.run(['$location', '$rootScope', 'Restangular', 'UserService', 'PlanServi } } + if (!Features.BILLING && response.status == 402) { + $('#overlicenseModal').modal({}); + return false; + } + if (response.status == 500) { document.location = '/500'; return false; diff --git a/static/js/controllers.js b/static/js/controllers.js index e063630df..3a0d4bd1c 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -1589,7 +1589,7 @@ function RepoAdminCtrl($scope, Restangular, ApiService, KeyService, $routeParams } function UserAdminCtrl($scope, $timeout, $location, ApiService, PlanService, UserService, CookieService, KeyService, - $routeParams, $http, UIService, Features) { + $routeParams, $http, UIService, Features, Config) { $scope.Features = Features; if ($routeParams['migrate']) { @@ -1597,11 +1597,9 @@ function UserAdminCtrl($scope, $timeout, $location, ApiService, PlanService, Use } UserService.updateUserIn($scope, function(user) { - if (!Features.GITHUB_LOGIN) { return; } - $scope.cuser = jQuery.extend({}, user); - if ($scope.cuser.logins) { + if (Features.GITHUB_LOGIN && $scope.cuser.logins) { for (var i = 0; i < $scope.cuser.logins.length; i++) { if ($scope.cuser.logins[i].service == 'github') { var githubId = $scope.cuser.logins[i].service_identifier; @@ -1694,6 +1692,8 @@ function UserAdminCtrl($scope, $timeout, $location, ApiService, PlanService, Use }; $scope.reallyConvert = function() { + if (Config.AUTHENTICATION_TYPE != 'Database') { return; } + $scope.loading = true; var data = { diff --git a/static/partials/organizations.html b/static/partials/organizations.html index ba591faae..4eaae8ca6 100644 --- a/static/partials/organizations.html +++ b/static/partials/organizations.html @@ -10,7 +10,7 @@ Create New Organization - + Convert account diff --git a/static/partials/user-admin.html b/static/partials/user-admin.html index 25ef0ec32..b39f2c445 100644 --- a/static/partials/user-admin.html +++ b/static/partials/user-admin.html @@ -38,7 +38,9 @@ Usage Logs - Convert to Organization + + Convert to Organization + @@ -197,7 +199,7 @@ - + diff --git a/templates/index.html b/templates/index.html index 56d9d086d..3da157cf2 100644 --- a/templates/index.html +++ b/templates/index.html @@ -35,4 +35,23 @@ +{% if not has_billing %} + + + + + + Cannot create user + + + A new user cannot be created as this organization has reached its licensed seat count. Please contact your administrator. + + + + + +{% endif %} + {% endblock %}