def ubuntu_woff_static_get(): static_file = static.StaticFile(settings.conf.www_path, 'fonts/ubuntu-bold.woff', cache=True) return static_file.get_response()
def user_linked_key_page_get(short_code): short_code = short_code[:128] remote_addr = utils.get_remote_addr() doc = _find_doc({ 'short_id': short_code, }, one_time=True, one_time_new=True) if not doc: journal.entry( journal.USER_PROFILE_FAILURE, remote_address=remote_addr, event_long='Key ID not found', ) return flask.abort(404) org = organization.get_by_id(doc['org_id']) usr = org.get_user(id=doc['user_id']) if usr.disabled: journal.entry( journal.USER_PROFILE_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='User disabled', ) return flask.abort(403) journal.entry( journal.USER_PROFILE_SUCCESS, usr.journal_data, remote_address=remote_addr, event_long='User temporary profile link viewed', ) usr.audit_event( 'user_profile', 'User temporary profile link viewed', remote_addr=remote_addr, ) if settings.local.sub_active and settings.app.theme == 'dark': view_name = KEY_VIEW_DARK_NAME else: view_name = KEY_VIEW_NAME if RADIUS_AUTH in usr.auth_type or \ settings.user.pin_mode == PIN_DISABLED: header_class = 'pin-disabled' else: header_class = '' if settings.user.restrict_import: header_class += ' restrict-import' key_page = static.StaticFile(settings.conf.www_path, view_name, cache=False, gzip=False).data uri_url = (utils.get_url_root() + '/ku/' + doc['short_id']).encode() if uri_url.startswith('https'): uri_url = uri_url.replace('https', 'pritunl', 1) else: uri_url = uri_url.replace('http', 'pritunl', 1) key_page = key_page.replace('<%= uri_url %>', uri_url) key_page = key_page.replace('<%= user_name %>', '%s - %s' % (org.name, usr.name)) key_page = key_page.replace('<%= user_key_tar_url %>', '/key/%s.tar' % (doc['key_id'])) key_page = key_page.replace('<%= user_key_zip_url %>', '/key/%s.zip' % (doc['key_id'])) if org.otp_auth and not usr.has_duo_passcode and not usr.has_yubikey: key_page = key_page.replace('<%= user_otp_key %>', usr.otp_secret) key_page = key_page.replace( '<%= user_otp_url %>', 'otpauth://totp/%s@%s?secret=%s' % (usr.name, org.name, usr.otp_secret)) else: key_page = key_page.replace('<%= user_otp_key %>', '') key_page = key_page.replace('<%= user_otp_url %>', '') if usr.pin: key_page = key_page.replace('<%= cur_pin_display %>', 'block') else: key_page = key_page.replace('<%= cur_pin_display %>', 'none') key_page = key_page.replace('<%= key_id %>', doc['key_id']) key_page = key_page.replace('<%= short_id %>', doc['short_id']) conf_links = '' if settings.local.sub_active: conf_links += '<a class="btn btn-success download-chrome" ' + \ 'title="Download Chrome OS Profile" ' + \ 'href="/key_onc/%s.onc">Download Chrome OS Profile</a>\n' % ( doc['key_id']) has_servers = False for server in usr.iter_servers(): has_servers = True conf_links += '<a class="btn btn-sm download-profile" ' + \ 'title="Download Profile" ' + \ 'href="/key/%s/%s.key">Download Profile (%s)</a>\n' % ( doc['key_id'], server.id, server.name) key_page = key_page.replace('<%= conf_links %>', conf_links) if not has_servers: header_class += ' no-servers' key_page = key_page.replace('<%= header_class %>', header_class) return key_page
def fredoka_woff_static_get(): static_file = static.StaticFile(settings.conf.www_path, 'fonts/fredoka-one.woff', cache=True) return static_file.get_response()
def favicon_static_get(): static_file = static.StaticFile(settings.conf.www_path, 'favicon.ico', cache=True) return static_file.get_response()
def robots_static_get(): static_file = static.StaticFile(settings.conf.www_path, 'robots.txt', cache=True) return static_file.get_response()
def sso_callback_get(): sso_mode = settings.app.sso if sso_mode not in (AZURE_AUTH, AZURE_DUO_AUTH, AZURE_YUBICO_AUTH, GOOGLE_AUTH, GOOGLE_DUO_AUTH, GOOGLE_YUBICO_AUTH, AUTHZERO_AUTH, AUTHZERO_DUO_AUTH, AUTHZERO_YUBICO_AUTH, SLACK_AUTH, SLACK_DUO_AUTH, SLACK_YUBICO_AUTH, SAML_AUTH, SAML_DUO_AUTH, SAML_YUBICO_AUTH, SAML_OKTA_AUTH, SAML_OKTA_DUO_AUTH, SAML_OKTA_YUBICO_AUTH, SAML_ONELOGIN_AUTH, SAML_ONELOGIN_DUO_AUTH, SAML_ONELOGIN_YUBICO_AUTH): return flask.abort(405) remote_addr = utils.get_remote_addr() state = flask.request.args.get('state') sig = flask.request.args.get('sig') tokens_collection = mongo.get_collection('sso_tokens') doc = tokens_collection.find_and_modify(query={ '_id': state, }, remove=True) if not doc: return flask.abort(404) query = flask.request.query_string.split('&sig=')[0] test_sig = base64.urlsafe_b64encode( hmac.new(str(doc['secret']), query, hashlib.sha512).digest()) if not utils.const_compare(sig, test_sig): journal.entry( journal.SSO_AUTH_FAILURE, state=state, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_INVALID_CALLBACK, reason_long='Signature mismatch', ) return flask.abort(401) params = urlparse.parse_qs(query) if doc.get('type') == SAML_AUTH: username = params.get('username')[0].lower() email = params.get('email', [None])[0] org_names = [] if params.get('org'): org_names_param = params.get('org')[0] if ';' in org_names_param: org_names = org_names_param.split(';') else: org_names = org_names_param.split(',') org_names = [x for x in org_names if x] org_names = sorted(org_names) groups = [] if params.get('groups'): groups_param = params.get('groups')[0] if ';' in groups_param: groups = groups_param.split(';') else: groups = groups_param.split(',') groups = [x for x in groups if x] groups = set(groups) if not username: return flask.abort(406) org_id = settings.app.sso_org if org_names: not_found = False for org_name in org_names: org = organization.get_by_name( utils.filter_unicode(org_name), fields=('_id'), ) if org: not_found = False org_id = org.id break else: not_found = True if not_found: logger.warning( 'Supplied org names do not exists', 'sso', sso_type=doc.get('type'), user_name=username, user_email=email, org_names=org_names, ) valid, org_id_new, groups2 = sso.plugin_sso_authenticate( sso_type='saml', user_name=username, user_email=email, remote_ip=remote_addr, sso_org_names=org_names, ) if valid: org_id = org_id_new or org_id else: logger.error( 'Saml plugin authentication not valid', 'sso', username=username, ) journal.entry( journal.SSO_AUTH_FAILURE, user_name=username, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_PLUGIN_FAILED, reason_long='Saml plugin authentication failed', ) return flask.abort(401) groups = groups | set(groups2 or []) elif doc.get('type') == SLACK_AUTH: username = params.get('username')[0].lower() email = None user_team = params.get('team')[0] org_names = params.get('orgs', [''])[0] org_names = sorted(org_names.split(',')) if user_team != settings.app.sso_match[0]: journal.entry( journal.SSO_AUTH_FAILURE, user_name=username, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_SLACK_FAILED, reason_long='Slack team not valid', ) return flask.abort(401) not_found = False org_id = settings.app.sso_org for org_name in org_names: org = organization.get_by_name( utils.filter_unicode(org_name), fields=('_id'), ) if org: not_found = False org_id = org.id break else: not_found = True if not_found: logger.warning( 'Supplied org names do not exists', 'sso', sso_type=doc.get('type'), user_name=username, user_email=email, org_names=org_names, ) valid, org_id_new, groups = sso.plugin_sso_authenticate( sso_type='slack', user_name=username, user_email=email, remote_ip=remote_addr, sso_org_names=org_names, ) if valid: org_id = org_id_new or org_id else: logger.error( 'Slack plugin authentication not valid', 'sso', username=username, ) journal.entry( journal.SSO_AUTH_FAILURE, user_name=username, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_PLUGIN_FAILED, reason_long='Slack plugin authentication failed', ) return flask.abort(401) groups = set(groups or []) elif doc.get('type') == GOOGLE_AUTH: username = params.get('username')[0].lower() email = username valid, google_groups = sso.verify_google(username) if not valid: journal.entry( journal.SSO_AUTH_FAILURE, user_name=username, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_GOOGLE_FAILED, reason_long='Google authentication failed', ) return flask.abort(401) org_id = settings.app.sso_org valid, org_id_new, groups = sso.plugin_sso_authenticate( sso_type='google', user_name=username, user_email=email, remote_ip=remote_addr, ) if valid: org_id = org_id_new or org_id else: logger.error( 'Google plugin authentication not valid', 'sso', username=username, ) journal.entry( journal.SSO_AUTH_FAILURE, user_name=username, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_PLUGIN_FAILED, reason_long='Google plugin authentication failed', ) return flask.abort(401) groups = set(groups or []) if settings.app.sso_google_mode == 'groups': groups = groups | set(google_groups) else: not_found = False google_groups = sorted(google_groups) for org_name in google_groups: org = organization.get_by_name( utils.filter_unicode(org_name), fields=('_id'), ) if org: not_found = False org_id = org.id break else: not_found = True if not_found: logger.warning( 'Supplied org names do not exists', 'sso', sso_type=doc.get('type'), user_name=username, user_email=email, org_names=google_groups, ) elif doc.get('type') == AZURE_AUTH: username = params.get('username')[0].lower() email = None tenant, username = username.split('/', 2) if tenant != settings.app.sso_azure_directory_id: logger.error( 'Azure directory ID mismatch', 'sso', username=username, ) journal.entry( journal.SSO_AUTH_FAILURE, user_name=username, azure_tenant=tenant, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_AZURE_FAILED, reason_long='Azure directory ID mismatch', ) return flask.abort(401) valid, azure_groups = sso.verify_azure(username) if not valid: journal.entry( journal.SSO_AUTH_FAILURE, user_name=username, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_AZURE_FAILED, reason_long='Azure authentication failed', ) return flask.abort(401) org_id = settings.app.sso_org valid, org_id_new, groups = sso.plugin_sso_authenticate( sso_type='azure', user_name=username, user_email=email, remote_ip=remote_addr, ) if valid: org_id = org_id_new or org_id else: logger.error( 'Azure plugin authentication not valid', 'sso', username=username, ) journal.entry( journal.SSO_AUTH_FAILURE, user_name=username, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_PLUGIN_FAILED, reason_long='Azure plugin authentication failed', ) return flask.abort(401) groups = set(groups or []) if settings.app.sso_azure_mode == 'groups': groups = groups | set(azure_groups) else: not_found = False azure_groups = sorted(azure_groups) for org_name in azure_groups: org = organization.get_by_name( utils.filter_unicode(org_name), fields=('_id'), ) if org: not_found = False org_id = org.id break else: not_found = True if not_found: logger.warning( 'Supplied org names do not exists', 'sso', sso_type=doc.get('type'), user_name=username, user_email=email, org_names=azure_groups, ) elif doc.get('type') == AUTHZERO_AUTH: username = params.get('username')[0].lower() email = None valid, authzero_groups = sso.verify_authzero(username) if not valid: journal.entry( journal.SSO_AUTH_FAILURE, user_name=username, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_AUTHZERO_FAILED, reason_long='Auth0 authentication failed', ) return flask.abort(401) org_id = settings.app.sso_org valid, org_id_new, groups = sso.plugin_sso_authenticate( sso_type='authzero', user_name=username, user_email=email, remote_ip=remote_addr, ) if valid: org_id = org_id_new or org_id else: logger.error( 'Auth0 plugin authentication not valid', 'sso', username=username, ) journal.entry( journal.SSO_AUTH_FAILURE, user_name=username, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_PLUGIN_FAILED, reason_long='Auth0 plugin authentication failed', ) return flask.abort(401) groups = set(groups or []) if settings.app.sso_authzero_mode == 'groups': groups = groups | set(authzero_groups) else: not_found = False authzero_groups = sorted(authzero_groups) for org_name in authzero_groups: org = organization.get_by_name( utils.filter_unicode(org_name), fields=('_id'), ) if org: not_found = False org_id = org.id break else: not_found = True if not_found: logger.warning( 'Supplied org names do not exists', 'sso', sso_type=doc.get('type'), user_name=username, user_email=email, org_names=authzero_groups, ) else: logger.error( 'Unknown sso type', 'sso', sso_type=doc.get('type'), ) return flask.abort(401) if DUO_AUTH in sso_mode: token = utils.generate_secret() tokens_collection = mongo.get_collection('sso_tokens') tokens_collection.insert({ '_id': token, 'type': DUO_AUTH, 'username': username, 'email': email, 'org_id': org_id, 'groups': list(groups) if groups else None, 'timestamp': utils.now(), }) duo_page = static.StaticFile(settings.conf.www_path, 'duo.html', cache=False, gzip=False) sso_duo_mode = settings.app.sso_duo_mode if sso_duo_mode == 'passcode': duo_mode = 'passcode' elif sso_duo_mode == 'phone': duo_mode = 'phone' else: duo_mode = 'push' body_class = duo_mode if settings.app.theme == 'dark': body_class += ' dark' duo_page.data = duo_page.data.replace('<%= body_class %>', body_class) duo_page.data = duo_page.data.replace('<%= token %>', token) duo_page.data = duo_page.data.replace('<%= duo_mode %>', duo_mode) return duo_page.get_response() if YUBICO_AUTH in sso_mode: token = utils.generate_secret() tokens_collection = mongo.get_collection('sso_tokens') tokens_collection.insert({ '_id': token, 'type': YUBICO_AUTH, 'username': username, 'email': email, 'org_id': org_id, 'groups': list(groups) if groups else None, 'timestamp': utils.now(), }) yubico_page = static.StaticFile(settings.conf.www_path, 'yubico.html', cache=False, gzip=False) if settings.app.theme == 'dark': yubico_page.data = yubico_page.data.replace( '<body>', '<body class="dark">') yubico_page.data = yubico_page.data.replace('<%= token %>', token) return yubico_page.get_response() return _validate_user(username, email, sso_mode, org_id, groups, remote_addr, http_redirect=True)
def logo_static_get(): static_file = static.StaticFile(settings.conf.www_path, 'logo.png', cache=True) return static_file.get_response()
def user_linked_key_page_get(short_code): doc = _find_doc({ 'short_id': short_code, }, one_time=True, one_time_new=True) if not doc: return flask.abort(404) org = organization.get_by_id(doc['org_id']) user = org.get_user(id=doc['user_id']) if user.disabled: return flask.abort(403) user.audit_event( 'user_profile', 'User temporary profile link viewed', remote_addr=utils.get_remote_addr(), ) if settings.local.sub_active and settings.app.theme == 'dark': view_name = KEY_VIEW_DARK_NAME else: view_name = KEY_VIEW_NAME if user.auth_type == RADIUS_AUTH or \ settings.user.pin_mode == PIN_DISABLED: header_class = 'pin-disabled' else: header_class = '' key_page = static.StaticFile(settings.conf.www_path, view_name, cache=False, gzip=False).data key_page = key_page.replace('<%= header_class %>', header_class) uri_url = (flask.request.url_root[:-1] + '/ku/' + doc['short_id']).encode() if uri_url.startswith('https'): uri_url = uri_url.replace('https', 'pritunl', 1) else: uri_url = uri_url.replace('http', 'pritunl', 1) key_page = key_page.replace('<%= uri_url %>', uri_url) key_page = key_page.replace('<%= user_name %>', '%s - %s' % (org.name, user.name)) key_page = key_page.replace('<%= user_key_tar_url %>', '/key/%s.tar' % (doc['key_id'])) key_page = key_page.replace('<%= user_key_zip_url %>', '/key/%s.zip' % (doc['key_id'])) if org.otp_auth: key_page = key_page.replace('<%= user_otp_key %>', user.otp_secret) key_page = key_page.replace( '<%= user_otp_url %>', 'otpauth://totp/%s@%s?secret=%s' % (user.name, org.name, user.otp_secret)) else: key_page = key_page.replace('<%= user_otp_key %>', '') key_page = key_page.replace('<%= user_otp_url %>', '') if user.pin: key_page = key_page.replace('<%= cur_pin_display %>', 'block') else: key_page = key_page.replace('<%= cur_pin_display %>', 'none') key_page = key_page.replace('<%= key_id %>', doc['key_id']) key_page = key_page.replace('<%= short_id %>', doc['short_id']) conf_links = '' if settings.local.sub_active: conf_links += '<a class="btn btn-success" ' + \ 'title="Download Chromebook Profiles" ' + \ 'href="/key_onc/%s.zip">Download Chromebook Profiles</a><br>\n' % ( doc['key_id']) for server in org.iter_servers(): conf_links += '<a class="btn btn-sm" title="Download Profile" ' + \ 'href="/key/%s/%s.key">Download Profile (%s)</a><br>\n' % ( doc['key_id'], server.id, server.name) key_page = key_page.replace('<%= conf_links %>', conf_links) return key_page
def sso_callback_get(): sso_mode = settings.app.sso if sso_mode not in (GOOGLE_AUTH, GOOGLE_DUO_AUTH, GOOGLE_YUBICO_AUTH, SLACK_AUTH, SLACK_DUO_AUTH, SLACK_YUBICO_AUTH, SAML_AUTH, SAML_DUO_AUTH, SAML_YUBICO_AUTH, SAML_OKTA_AUTH, SAML_OKTA_DUO_AUTH, SAML_OKTA_YUBICO_AUTH, SAML_ONELOGIN_AUTH, SAML_ONELOGIN_DUO_AUTH, SAML_ONELOGIN_YUBICO_AUTH): return flask.abort(405) state = flask.request.args.get('state') sig = flask.request.args.get('sig') tokens_collection = mongo.get_collection('sso_tokens') doc = tokens_collection.find_and_modify(query={ '_id': state, }, remove=True) if not doc: return flask.abort(404) query = flask.request.query_string.split('&sig=')[0] test_sig = base64.urlsafe_b64encode(hmac.new(str(doc['secret']), query, hashlib.sha512).digest()) if not utils.const_compare(sig, test_sig): return flask.abort(401) params = urlparse.parse_qs(query) if doc.get('type') == SAML_AUTH: username = params.get('username')[0] email = params.get('email', [None])[0] org_name = params.get('org', [None])[0] if not username: return flask.abort(406) org_id = settings.app.sso_org if org_name: org = organization.get_by_name(org_name, fields=('_id')) if org: org_id = org.id valid, org_id_new, groups = sso.plugin_sso_authenticate( sso_type='saml', user_name=username, user_email=email, remote_ip=utils.get_remote_addr(), sso_org_names=[org_name], ) if valid: org_id = org_id_new or org_id else: logger.error('Saml plugin authentication not valid', 'sso', username=username, ) return flask.abort(401) elif doc.get('type') == SLACK_AUTH: username = params.get('username')[0] email = None user_team = params.get('team')[0] org_names = params.get('orgs', [''])[0] org_names = org_names.split(',') valid = sso.verify_slack(username, user_team) if not valid: return flask.abort(401) org_id = settings.app.sso_org for org_name in org_names: org = organization.get_by_name(org_name, fields=('_id')) if org: org_id = org.id break valid, org_id_new, groups = sso.plugin_sso_authenticate( sso_type='slack', user_name=username, user_email=email, remote_ip=utils.get_remote_addr(), sso_org_names=org_names, ) if valid: org_id = org_id_new or org_id else: logger.error('Slack plugin authentication not valid', 'sso', username=username, ) return flask.abort(401) else: username = params.get('username')[0] email = username valid = sso.verify_google(username) if not valid: return flask.abort(401) org_id = settings.app.sso_org valid, org_id_new, groups = sso.plugin_sso_authenticate( sso_type='google', user_name=username, user_email=email, remote_ip=utils.get_remote_addr(), ) if valid: org_id = org_id_new or org_id else: logger.error('Google plugin authentication not valid', 'sso', username=username, ) return flask.abort(401) if DUO_AUTH in sso_mode: token = utils.generate_secret() tokens_collection = mongo.get_collection('sso_tokens') tokens_collection.insert({ '_id': token, 'type': DUO_AUTH, 'username': username, 'email': email, 'org_id': org_id, 'groups': groups, 'timestamp': utils.now(), }) duo_page = static.StaticFile(settings.conf.www_path, 'duo.html', cache=False, gzip=False) sso_duo_mode = settings.app.sso_duo_mode if sso_duo_mode == 'passcode': duo_mode = 'passcode' elif sso_duo_mode == 'phone': duo_mode = 'phone' else: duo_mode = 'push' body_class = duo_mode if settings.app.theme == 'dark': body_class += ' dark' duo_page.data = duo_page.data.replace('<%= body_class %>', body_class) duo_page.data = duo_page.data.replace('<%= token %>', token) duo_page.data = duo_page.data.replace('<%= duo_mode %>', duo_mode) return duo_page.get_response() if YUBICO_AUTH in sso_mode: token = utils.generate_secret() tokens_collection = mongo.get_collection('sso_tokens') tokens_collection.insert({ '_id': token, 'type': YUBICO_AUTH, 'username': username, 'email': email, 'org_id': org_id, 'groups': groups, 'timestamp': utils.now(), }) yubico_page = static.StaticFile(settings.conf.www_path, 'yubico.html', cache=False, gzip=False) if settings.app.theme == 'dark': yubico_page.data = yubico_page.data.replace( '<body>', '<body class="dark">') yubico_page.data = yubico_page.data.replace('<%= token %>', token) return yubico_page.get_response() usr = user.find_user_auth(name=username, auth_type=sso_mode) if not usr: org = organization.get_by_id(org_id) if not org: return flask.abort(405) usr = org.find_user(name=username) else: org = usr.org if not usr: usr = org.new_user(name=username, email=email, type=CERT_CLIENT, auth_type=sso_mode, groups=list(groups) if groups else None) usr.audit_event('user_created', 'User created with single sign-on', remote_addr=utils.get_remote_addr()) event.Event(type=ORGS_UPDATED) event.Event(type=USERS_UPDATED, resource_id=org.id) event.Event(type=SERVERS_UPDATED) else: if usr.disabled: return flask.abort(403) if groups and groups - set(usr.groups or []): usr.groups = list(set(usr.groups or []) | groups) usr.commit('groups') if usr.auth_type != sso_mode: usr.auth_type = sso_mode usr.commit('auth_type') key_link = org.create_user_key_link(usr.id, one_time=True) usr.audit_event('user_profile', 'User profile viewed from single sign-on', remote_addr=utils.get_remote_addr(), ) return utils.redirect(utils.get_url_root() + key_link['view_url'])
def user_linked_key_page_get(short_code): doc = _find_doc({ 'short_id': short_code, }, one_time=True) if not doc: return flask.abort(404) org = organization.get_by_id(doc['org_id']) user = org.get_user(id=doc['user_id']) user.audit_event( 'user_profile', 'User temporary profile link viewed', remote_addr=utils.get_remote_addr(), ) if settings.local.sub_active and settings.app.theme == 'dark': view_name = KEY_VIEW_DARK_NAME else: view_name = KEY_VIEW_NAME key_page = static.StaticFile(settings.conf.www_path, view_name, cache=False).data key_page = key_page.replace('<%= user_name %>', '%s - %s' % (org.name, user.name)) key_page = key_page.replace('<%= user_key_tar_url %>', '/key/%s.tar' % (doc['key_id'])) key_page = key_page.replace('<%= user_key_zip_url %>', '/key/%s.zip' % (doc['key_id'])) if org.otp_auth: key_page = key_page.replace('<%= user_otp_key %>', user.otp_secret) key_page = key_page.replace( '<%= user_otp_url %>', 'otpauth://totp/%s@%s?secret=%s' % (user.name, org.name, user.otp_secret)) else: key_page = key_page.replace('<%= user_otp_key %>', '') key_page = key_page.replace('<%= user_otp_url %>', '') if user.pin: key_page = key_page.replace('<%= cur_pin_display %>', 'block') else: key_page = key_page.replace('<%= cur_pin_display %>', 'none') key_page = key_page.replace('<%= key_id %>', doc['key_id']) key_page = key_page.replace('<%= short_id %>', doc['short_id']) conf_links = '' if settings.local.sub_active: conf_links += '<a class="btn btn-success" ' + \ 'title="Download Chromebook Profiles" ' + \ 'href="/key_onc/%s.zip">Download Chromebook Profiles</a><br>\n' % ( doc['key_id']) for server in org.iter_servers(): conf_links += '<a class="btn btn-sm" title="Download Profile" ' + \ 'href="/key/%s/%s.key">Download Profile (%s)</a><br>\n' % ( doc['key_id'], server.id, server.name) key_page = key_page.replace('<%= conf_links %>', conf_links) return key_page