def testTurnOnRecaptchaIfRecaptchaWasOffAndFailedAttemptsOverMax(self): """Test recaptcha turns on if it was off and went over max failed login.""" # Add enough failed attempts to the DB after start datetime to hit max. for x in range(ufo.MAX_FAILED_LOGINS_BEFORE_RECAPTCHA): models.FailedLoginAttempt.create() # Set the config to show recaptcha with an end datetime of now. config = ufo.get_user_config() config.should_show_recaptcha = False config.save() test_datetime = datetime.datetime.now() should_show_recaptcha, failed_attempts_count = auth.determine_if_recaptcha_should_be_turned_on_or_off() self.assertTrue(should_show_recaptcha) self.assertEquals(ufo.MAX_FAILED_LOGINS_BEFORE_RECAPTCHA, failed_attempts_count) self.assertEquals(ufo.MAX_FAILED_LOGINS_BEFORE_RECAPTCHA, len(models.FailedLoginAttempt.get_all())) config = ufo.get_user_config() self.assertTrue(config.should_show_recaptcha) self.assertTrue(config.recaptcha_start_datetime >= test_datetime) self.assertTrue(config.recaptcha_end_datetime >= test_datetime) new_delta = config.recaptcha_end_datetime - config.recaptcha_start_datetime default_delta = datetime.timedelta( minutes=auth.INITIAL_RECAPTCHA_TIMEFRAME_MINUTES) self.assertEquals(default_delta, new_delta)
def add_user(): """Gets the requested users on get and stores new user(s) on post. This one handler does get and post for add user. The get method is handled by _render_user_add with the parameters for get_all, group_key, and user_key each passed along. The post method is handled here by inserting the new user or users into the database and redirecting to the user_list page. Returns: A list of the requested users for a get or redirects to the user_list page after inserting users for a post. """ if flask.request.method == 'GET': get_all = flask.request.args.get('get_all') group_key = flask.request.args.get('group_key') user_key = flask.request.args.get('user_key') return _get_users_to_add(get_all, group_key, user_key) manual = flask.request.form.get('manual') users_list = json.loads(flask.request.form.get('users')) config = ufo.get_user_config() for submitted_user in users_list: db_user = models.User() db_user.name = submitted_user['name'] db_user.email = submitted_user['email'] db_user.domain = config.domain if manual is None else None # Save on each user so that we can let the database check if the # uniqueness constraint is fulfilled. i.e don't batch this because # if one user is added more than once then the whole session will fail. try: db_user.save() except custom_exceptions.UnableToSaveToDB as e: flask.abort(e.code, e.message) return user_list()
def _make_chrome_policy_json(): """Generates the json string of chrome policy based on values in the db. This policy string has the following form: { "validProxyServers": {"Value": map_of_proxy_server_ips_to_public_key}, "enforceProxyServerValidity": {"Value": boolean} } Returns: A json string of current chrome policy. """ proxy_servers = models.ProxyServer.query.all() proxy_server_dict = {} for server in proxy_servers: proxy_server_dict[server.ip_address] = ( server.get_public_key_as_authorization_file_string()) proxy_server_value_dict = {"Value" : proxy_server_dict} config = ufo.get_user_config() config_value_dict = {"Value" : config.proxy_server_validity} policy_dictionary = { "validProxyServers": proxy_server_value_dict, "enforceProxyServerValidity": config_value_dict, } return json.dumps(policy_dictionary)
def setup_admin(): """Create the first admin in the system from the setup page. Returns: A json message indicating a redirect to login if successful. """ config = ufo.get_user_config() if config.isConfigured: flask.abort(403, ufo.get_json_message('cantSetAdminAfterInitialSetupError')) admin_email = flask.request.form.get('admin_email', None) admin_password = flask.request.form.get('admin_password', None) if admin_email is None or admin_password is None: flask.abort(403, ufo.get_json_message('noAdministratorError')) admin_user = models.AdminUser(email=admin_email) admin_user.set_password(admin_password) admin_user.save() config.isConfigured = True config.should_show_recaptcha = False config.save() response_json = json.dumps(({'shouldRedirect': True})) return flask.Response(ufo.XSSI_PREFIX + response_json, headers=ufo.JSON_HEADERS)
def _make_chrome_policy_json(): """Generates the json string of chrome policy based on values in the db. This policy string has the following form: { "validProxyServers": {"Value": map_of_proxy_server_ips_to_public_key}, "enforceProxyServerValidity": {"Value": boolean} } Returns: A json string of current chrome policy. """ proxy_servers = models.ProxyServer.query.all() proxy_server_dict = {} for server in proxy_servers: proxy_server_dict[server.ip_address] = ( server.get_public_key_as_authorization_file_string()) proxy_server_value_dict = {"Value": proxy_server_dict} config = ufo.get_user_config() config_value_dict = {"Value": config.proxy_server_validity} policy_dictionary = { "validProxyServers": proxy_server_value_dict, "enforceProxyServerValidity": config_value_dict, } return json.dumps(policy_dictionary)
def GetUsers(self): """Get the users of a domain. Returns: A list of users. Raises: googleapiclient.errors.HttpError on failure to find the domain. """ config = ufo.get_user_config() users = [] page_token = '' while True: request = self.service.users().list(domain=config.domain, maxResults=500, pageToken=page_token, projection='full', orderBy='email') result = request.execute(num_retries=NUM_RETRIES) users += result['users'] if 'nextPageToken' in result: page_token = result['nextPageToken'] else: break return users
def setup_oauth(): """Setup oauth for the apps domain. Returns: A json message indicating success or a flask abort with 403 for oauth exceptions. """ oauth_code = flask.request.form.get('oauth_code', None) if oauth_code is None: flask.abort(403, ufo.get_json_message('noOauthCodeError')) config = ufo.get_user_config() flow = oauth.getOauthFlow() credentials = None domain = flask.request.form.get('domain', None) try: credentials = flow.step2_exchange(oauth_code) except oauth2client.client.FlowExchangeError as e: flask.abort(403, e.message) apiClient = credentials.authorize(httplib2.Http()) plusApi = discovery.build(serviceName='plus', version='v1', http=apiClient) adminApi = discovery.build(serviceName='admin', version='directory_v1', http=apiClient) profileResult = None try: profileResult = plusApi.people().get(userId='me').execute() except Exception as e: ufo.app.logger.error(e, exc_info=True) flask.abort(403, ufo.get_json_message('domainInvalidError')) if domain is None or domain != profileResult.get('domain', None): flask.abort(403, ufo.get_json_message('domainInvalidError')) user_id = profileResult['id'] userResult = None try: userResult = adminApi.users().get(userKey=user_id).execute() except Exception as e: ufo.app.logger.error(e, exc_info=True) flask.abort(403, ufo.get_json_message('nonAdminAccessError')) if not userResult.get('isAdmin', False): flask.abort(403, ufo.get_json_message('nonAdminAccessError')) config.credentials = credentials.to_json() config.domain = domain flask.session['domain'] = domain config.save() response_dict = {'domain': domain, 'credentials': config.credentials} response_json = json.dumps((response_dict)) return flask.Response(ufo.XSSI_PREFIX + response_json, headers=ufo.JSON_HEADERS)
def setup_oauth(): """Setup oauth for the apps domain. Returns: A json message indicating success or a flask abort with 403 for oauth exceptions. """ oauth_code = flask.request.form.get('oauth_code', None) if oauth_code is None: flask.abort(403, ufo.get_json_message('noOauthCodeError')) config = ufo.get_user_config() flow = oauth.getOauthFlow() credentials = None domain = flask.request.form.get('domain', None) try: credentials = flow.step2_exchange(oauth_code) except oauth2client.client.FlowExchangeError as e: flask.abort(403, e.message) apiClient = credentials.authorize(httplib2.Http()) plusApi = discovery.build(serviceName='plus', version='v1', http=apiClient) adminApi = discovery.build(serviceName='admin', version='directory_v1', http = apiClient) profileResult = None try: profileResult = plusApi.people().get(userId='me').execute() except Exception as e: ufo.app.logger.error(e, exc_info=True) flask.abort(403, ufo.get_json_message('domainInvalidError')) if domain is None or domain != profileResult.get('domain', None): flask.abort(403, ufo.get_json_message('domainInvalidError')) user_id = profileResult['id'] userResult = None try: userResult = adminApi.users().get(userKey=user_id).execute() except Exception as e: ufo.app.logger.error(e, exc_info=True) flask.abort(403, ufo.get_json_message('nonAdminAccessError')) if not userResult.get('isAdmin', False): flask.abort(403, ufo.get_json_message('nonAdminAccessError')) config.credentials = credentials.to_json() config.domain = domain flask.session['domain'] = domain config.save() response_dict = {'domain': domain, 'credentials': config.credentials} response_json = json.dumps((response_dict)) return flask.Response(ufo.XSSI_PREFIX + response_json, headers=ufo.JSON_HEADERS)
def getOauthFlow(): config = ufo.get_user_config() # TODO give user an option to input their own and go through a different flow # to handle it return client.OAuth2WebServerFlow( client_id=ufo.app.config.get('SHARED_OAUTH_CLIENT_ID'), client_secret=ufo.app.config.get('SHARED_OAUTH_CLIENT_SECRET'), scope=util.scopes_to_string(OAUTH_SCOPES), redirect_uri='urn:ietf:wg:oauth:2.0:oob', )
def testEditSettings(self): """Test posting with modified settings updates in the db.""" config = ufo.get_user_config() initial_proxy_server_config = config.proxy_server_validity initial_network_jail_config = config.network_jail_until_google_auth data_to_post = { 'enforce_proxy_server_validity': json.dumps(not initial_proxy_server_config), 'enforce_network_jail': json.dumps(not initial_network_jail_config), } resp = self.client.post(flask.url_for('edit_settings'), data=data_to_post, follow_redirects=False) updated_config = ufo.get_user_config() self.assertEqual(not initial_proxy_server_config, updated_config.proxy_server_validity) # Uncomment when network jail is available. #self.assertEqual(not initial_network_jail_config, # updated_config.network_jail_until_google_auth) self.assert_redirects(resp, flask.url_for('get_settings'))
def sync_db_users_against_directory_service(self): """Checks whether the users currently in the DB are still valid. This gets all users in the DB, finds those that match the current domain, and compares them to those found in the domain in Google Directory Service. If a user in the DB is not the domain, then it is presumed to be deleted and will thus be removed from our DB. """ ufo.app.logger.info('Starting user sync.') db_users = models.User.query.all() directory_users = {} config = ufo.get_user_config() with ufo.app.app_context(): credentials = oauth.getSavedCredentials() # TODO this should handle the case where we do not have oauth if not credentials: ufo.app.logger.info('OAuth credentials not set up. Can\'t sync users.') return try: directory_service = google_directory_service.GoogleDirectoryService( credentials) directory_users = directory_service.GetUsersAsDictionary() except errors.HttpError as error: ufo.app.logger.info('Error encountered while requesting users from ' 'directory service: ' + str(error)) return for db_user in db_users: # Don't worry about users from another domain since they won't show up. if db_user.domain != config.domain: ufo.app.logger.info('User ' + db_user.email + ' did not match the ' 'current domain. Ignoring in directory service.') continue # Lookup user in dictionary based on email field. directory_user = directory_users.get(db_user.email, None) # TODO(eholder): Unit test the conditionals here. # Assume deleted if not found, so delete from our db. if directory_user is None: ufo.app.logger.info('User ' + db_user.email + ' was not found in ' 'directory service.') self.perform_configured_action_on_user(config.user_delete_action, db_user) continue if directory_user['suspended']: ufo.app.logger.info('User ' + db_user.email + ' was suspended in ' 'directory service.') self.perform_configured_action_on_user(config.user_revoke_action, db_user) continue
def getSavedCredentials(): credentials = getattr(flask.g, '_credentials', None) if not credentials: config = ufo.get_user_config() if not config.credentials: return None credentials = client.OAuth2Credentials.from_json(config.credentials) flask.g._credentials = credentials return credentials
def testRecaptchaStaysOffIfNotConfigured(self): """Test recaptcha does not turn on if not configured.""" # Add enough failed attempts to the DB after start datetime to hit max. for x in range(ufo.MAX_FAILED_LOGINS_BEFORE_RECAPTCHA): models.FailedLoginAttempt.create() # Set the config to show recaptcha with an end datetime of now. config = ufo.get_user_config() config.should_show_recaptcha = False config.save() ufo.RECAPTCHA_ENABLED_FOR_APP = False test_datetime = datetime.datetime.now() should_show_recaptcha, failed_attempts_count = auth.determine_if_recaptcha_should_be_turned_on_or_off() self.assertFalse(should_show_recaptcha) self.assertEquals(ufo.MAX_FAILED_LOGINS_BEFORE_RECAPTCHA, failed_attempts_count) self.assertEquals(ufo.MAX_FAILED_LOGINS_BEFORE_RECAPTCHA, len(models.FailedLoginAttempt.get_all())) config = ufo.get_user_config() self.assertFalse(config.should_show_recaptcha)
def _make_settings_json(): """Generates the json string of all settings based on values in the db. Returns: A json string of current server settings. """ config = ufo.get_user_config() settings_dictionary = { "enforce_network_jail": config.network_jail_until_google_auth, "enforce_proxy_server_validity": config.proxy_server_validity, } return json.dumps(settings_dictionary)
def testTurnOffRecaptchaIfRecaptchaWasOnAndTimedOut(self): """Test recaptcha turns off if it was on and timed out.""" start_datetime = datetime.datetime.now() # Add some failed attempts to the DB after start datetime. expected_count = 3 for x in range(expected_count): models.FailedLoginAttempt.create() # Set the config to show recaptcha with an end datetime of now. config = ufo.get_user_config() config.should_show_recaptcha = True config.recaptcha_start_datetime = start_datetime config.recaptcha_end_datetime = datetime.datetime.now() config.save() should_show_recaptcha, failed_attempts_count = auth.determine_if_recaptcha_should_be_turned_on_or_off() self.assertFalse(should_show_recaptcha) self.assertEquals(expected_count, failed_attempts_count) self.assertEquals(0, len(models.FailedLoginAttempt.get_all())) config = ufo.get_user_config() self.assertFalse(config.should_show_recaptcha)
def decorated(*args, **kwargs): """Requires that the user be logged in if the setup process has finished. Returns: A call to the decorated function if the setup process is incomplete or a user is logged in. Raises: NotLoggedIn: If the setup process is complete and a user is not logged in currently. """ config = ufo.get_user_config() if not config.isConfigured: return f(*args, **kwargs) if is_user_logged_in(): return f(*args, **kwargs) raise NotLoggedIn
def determine_if_recaptcha_should_be_turned_on_or_off(): """Determines whether or not to show the recaptcha and updates the DB. Return: A tuple where the first entry is a boolean for whether or not to show recaptcha and the second entry is the count of failed login attempts. """ config = ufo.get_user_config() failed_attempts_count = 0 now = datetime.datetime.now() if not ufo.RECAPTCHA_ENABLED_FOR_APP: delta = datetime.timedelta(minutes=INITIAL_RECAPTCHA_TIMEFRAME_MINUTES) failed_attempts_count = models.FailedLoginAttempt.count_since_datetime( now - delta) return False, failed_attempts_count if config.should_show_recaptcha: failed_attempts_count = models.FailedLoginAttempt.count_since_datetime( config.recaptcha_start_datetime) if config.recaptcha_end_datetime < now: # This is the case when the recaptcha was on and timed out so turn it off config.should_show_recaptcha = False models.FailedLoginAttempt.delete_before_datetime( config.recaptcha_end_datetime) elif failed_attempts_count >= ufo.MAX_FAILED_LOGINS_BEFORE_RECAPTCHA: # This is the case when the recaptcha was on and has since seen more # failures over the threshold, so it needs to be extended. delta = ( config.recaptcha_end_datetime - config.recaptcha_start_datetime) _turn_on_recaptcha(config, now, delta * 2) else: delta = datetime.timedelta(minutes=INITIAL_RECAPTCHA_TIMEFRAME_MINUTES) failed_attempts_count = models.FailedLoginAttempt.count_since_datetime( now - delta) if failed_attempts_count >= ufo.MAX_FAILED_LOGINS_BEFORE_RECAPTCHA: # This is the case when I need to turn on recaptcha initially. _turn_on_recaptcha(config, now, delta) config.save() return config.should_show_recaptcha, failed_attempts_count
def login(): """Logs a user into the management server. Returns: A redirect to the login page if there is any problem with the current user or a redirect to the landing page if everything checks out. """ if is_user_logged_in(): return flask.redirect(flask.url_for('landing')) if flask.request.method == 'GET': return _handle_login_get() email = flask.request.form.get('email') password = flask.request.form.get('password') user = models.AdminUser.get_by_email(email) if user is None: return flask.redirect( flask.url_for('login', error='No valid user found.')) # TODO(eholder): Add a functional test to verify that recaptcha does trigger, # but the test must be at the end since we won't be able to login afterwards. config = ufo.get_user_config() if (ufo.RECAPTCHA_ENABLED_FOR_APP and config.should_show_recaptcha and not ufo.RECAPTCHA.verify()): models.FailedLoginAttempt.create() return flask.redirect(flask.url_for('login', error='Failed recaptcha.')) if not user.does_password_match(password): models.FailedLoginAttempt.create() return flask.redirect(flask.url_for('login', error='Invalid password.')) flask.session['email'] = user.email flask.session['domain'] = config.domain return flask.redirect(flask.url_for('landing'))
def edit_settings(): """Receives the posted form for editing the policy config values. The new policy config values are stored in the database. Returns: A redirect back to display chrome policy with will display the new values. """ # TODO(eholder): Move the display of config values and the edit handlers to # something more sensible once UI review tells us what that should be. I'm # envisioning a settings or options page which is underneath the overall # Setup link. For now, I just want these settings to be edittable somewhere. config = ufo.get_user_config() # Uncomment the network jail when it's available. proxy_server_string = flask.request.form.get('enforce_proxy_server_validity') #network_jail_string = flask.request.form.get('enforce_network_jail') config.proxy_server_validity = json.loads(proxy_server_string) #config.network_jail_until_google_auth = json.loads(network_jail_string) config.save() return flask.redirect(flask.url_for('get_settings'))
def login(): """Logs a user into the management server. Returns: A redirect to the login page if there is any problem with the current user or a redirect to the landing page if everything checks out. """ if is_user_logged_in(): return flask.redirect(flask.url_for('landing')) if flask.request.method == 'GET': return _handle_login_get() email = flask.request.form.get('email') password = flask.request.form.get('password') user = models.AdminUser.get_by_email(email) if user is None: return flask.redirect(flask.url_for('login', error='No valid user found.')) # TODO(eholder): Add a functional test to verify that recaptcha does trigger, # but the test must be at the end since we won't be able to login afterwards. config = ufo.get_user_config() if (ufo.RECAPTCHA_ENABLED_FOR_APP and config.should_show_recaptcha and not ufo.RECAPTCHA.verify()): models.FailedLoginAttempt.create() return flask.redirect(flask.url_for('login', error='Failed recaptcha.')) if not user.does_password_match(password): models.FailedLoginAttempt.create() return flask.redirect(flask.url_for('login', error='Invalid password.')) flask.session['email'] = user.email flask.session['domain'] = config.domain flask.session['isConfigured'] = config.isConfigured return flask.redirect(flask.url_for('landing'))
def edit_settings(): """Receives the posted form for editing the policy config values. The new policy config values are stored in the database. Returns: A redirect back to display chrome policy with will display the new values. """ # TODO(eholder): Move the display of config values and the edit handlers to # something more sensible once UI review tells us what that should be. I'm # envisioning a settings or options page which is underneath the overall # Setup link. For now, I just want these settings to be edittable somewhere. config = ufo.get_user_config() # Uncomment the network jail when it's available. proxy_server_string = flask.request.form.get( 'enforce_proxy_server_validity') #network_jail_string = flask.request.form.get('enforce_network_jail') config.proxy_server_validity = json.loads(proxy_server_string) #config.network_jail_until_google_auth = json.loads(network_jail_string) config.save() return flask.redirect(flask.url_for('get_settings'))
def setup(): """Handle showing the user the setup page and processing the response. Returns: On get: a rendered setup page template with appropriate resources passed in. On post: a rendered setup page template with the error set in event of a known error, a 403 flask.abort in the event of a FlowExchangeError during oauth, or a redirect back to get the setup page on success. """ config = ufo.get_user_config() flow = oauth.getOauthFlow() oauth_url = flow.step1_get_authorize_url() oauth_resources_dict = _get_oauth_configration_resources_dict(config, oauth_url) if flask.request.method == 'GET': return flask.render_template( 'setup.html', oauth_url=oauth_url, oauth_configuration_resources=json.dumps(oauth_resources_dict)) credentials = None domain = flask.request.form.get('domain', None) if flask.request.form.get('oauth_code', None): try: credentials = flow.step2_exchange(flask.request.form['oauth_code']) except oauth2client.client.FlowExchangeError as e: flask.abort(403) # TODO better error apiClient = credentials.authorize(httplib2.Http()) plusApi = discovery.build(serviceName='plus', version='v1', http=apiClient) adminApi = discovery.build(serviceName='admin', version='directory_v1', http = apiClient) try: profileResult = plusApi.people().get(userId='me').execute() if domain is None or domain != profileResult.get('domain', None): return flask.render_template( 'setup.html', error=DOMAIN_INVALID_TEXT, oauth_configuration_resources=json.dumps(oauth_resources_dict)) user_id = profileResult['id'] userResult = adminApi.users().get(userKey=user_id).execute() if not userResult.get('isAdmin', False): return flask.render_template( 'setup.html', error=NON_ADMIN_TEXT, oauth_configuration_resources=json.dumps(oauth_resources_dict)) except Exception as e: ufo.app.logger.error(e, exc_info=True) return flask.render_template( 'setup.html', error=str(e), oauth_configuration_resources=json.dumps(oauth_resources_dict)) if not config.isConfigured: admin_email = flask.request.form.get('admin_email', None) admin_password = flask.request.form.get('admin_password', None) if admin_email is None or admin_password is None: return flask.render_template( 'setup.html', error=NO_ADMINISTRATOR, oauth_configuration_resources=json.dumps(oauth_resources_dict)) admin_user = models.AdminUser(email=admin_email) admin_user.set_password(admin_password) admin_user.save() # if credentials were set above, moved down here to give us a chance to error # out of admin user and password, could be moved inline with proper form # validation for that (we also don't want to create a user if another step # is going to fail) if credentials is not None: config.credentials = credentials.to_json() config.domain = domain flask.session['domain'] = domain config.isConfigured = True config.should_show_recaptcha = False config.save() return flask.redirect(flask.url_for('setup'))
def setup(): """Handle showing the user the setup page and processing the response. Returns: On get: a rendered setup page template with appropriate resources passed in. On post: a rendered setup page template with the error set in event of a known error, a 403 flask.abort in the event of a FlowExchangeError during oauth, or a redirect back to get the setup page on success. """ config = ufo.get_user_config() flow = oauth.getOauthFlow() oauth_url = flow.step1_get_authorize_url() oauth_resources_dict = _get_oauth_configration_resources_dict( config, oauth_url) if flask.request.method == 'GET': return flask.render_template( 'setup.html', oauth_url=oauth_url, oauth_configuration_resources=json.dumps(oauth_resources_dict)) credentials = None domain = flask.request.form.get('domain', None) if flask.request.form.get('oauth_code', None): try: credentials = flow.step2_exchange(flask.request.form['oauth_code']) except oauth2client.client.FlowExchangeError as e: flask.abort(403) # TODO better error apiClient = credentials.authorize(httplib2.Http()) plusApi = discovery.build(serviceName='plus', version='v1', http=apiClient) adminApi = discovery.build(serviceName='admin', version='directory_v1', http=apiClient) try: profileResult = plusApi.people().get(userId='me').execute() if domain is None or domain != profileResult.get('domain', None): return flask.render_template( 'setup.html', error=DOMAIN_INVALID_TEXT, oauth_configuration_resources=json.dumps( oauth_resources_dict)) user_id = profileResult['id'] userResult = adminApi.users().get(userKey=user_id).execute() if not userResult.get('isAdmin', False): return flask.render_template( 'setup.html', error=NON_ADMIN_TEXT, oauth_configuration_resources=json.dumps( oauth_resources_dict)) except Exception as e: ufo.app.logger.error(e, exc_info=True) return flask.render_template( 'setup.html', error=str(e), oauth_configuration_resources=json.dumps(oauth_resources_dict)) if not config.isConfigured: admin_email = flask.request.form.get('admin_email', None) admin_password = flask.request.form.get('admin_password', None) if admin_email is None or admin_password is None: return flask.render_template( 'setup.html', error=NO_ADMINISTRATOR, oauth_configuration_resources=json.dumps(oauth_resources_dict)) admin_user = models.AdminUser(email=admin_email) admin_user.set_password(admin_password) admin_user.save() # if credentials were set above, moved down here to give us a chance to error # out of admin user and password, could be moved inline with proper form # validation for that (we also don't want to create a user if another step # is going to fail) if credentials is not None: config.credentials = credentials.to_json() config.domain = domain flask.session['domain'] = domain config.isConfigured = True config.should_show_recaptcha = False config.save() return flask.redirect(flask.url_for('setup'))