From 205362bc7bd6412a6f108c2742d2faf9854ae64b Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Wed, 28 May 2014 15:22:36 -0400 Subject: [PATCH 1/2] Add UI for handling the case when an enterprise has reached its maximum seat count --- .gitignore | 3 +++ endpoints/api/__init__.py | 8 ++++++++ endpoints/api/user.py | 4 +++- endpoints/common.py | 1 + endpoints/index.py | 7 ++++++- static/js/app.js | 9 +++++++-- templates/index.html | 19 +++++++++++++++++++ 7 files changed, 47 insertions(+), 4 deletions(-) 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/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 4d54b3e50..f21b77926 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 @@ -193,6 +193,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) 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 bc843d4d1..f2aaae42e 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 75220520c..f17a87870 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -4749,8 +4749,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 || ''}); @@ -4764,6 +4764,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/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 %} + + +{% endif %} + {% endblock %} From 69be86be97f87acfaa87ccc9798ecef7b297a540 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Wed, 28 May 2014 15:53:53 -0400 Subject: [PATCH 2/2] Add extra seat check in the user API call and turn off user->org conversion when authentication is LDAP --- config.py | 2 +- endpoints/api/user.py | 8 +++++++- static/js/controllers.js | 8 ++++---- static/partials/organizations.html | 2 +- static/partials/user-admin.html | 6 ++++-- 5 files changed, 17 insertions(+), 9 deletions(-) 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/user.py b/endpoints/api/user.py index f21b77926..ee5e2e5e5 100644 --- a/endpoints/api/user.py +++ b/endpoints/api/user.py @@ -229,7 +229,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} @@ -247,6 +252,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/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 - +