def _auth_radius(username, password): valid, org_id = sso.verify_radius(username, password) if not valid: return utils.jsonify({ 'error': AUTH_INVALID, 'error_msg': AUTH_INVALID_MSG, }, 401) if not org_id: org_id = settings.app.sso_org org = organization.get_by_id(org_id) if not org: return flask.abort(405) usr = org.find_user(name=username) if not usr: usr = org.new_user(name=username, type=CERT_CLIENT, auth_type=RADIUS_AUTH) 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 utils.jsonify({ 'error': AUTH_DISABLED, 'error_msg': AUTH_DISABLED_MSG, }, 403) if usr.auth_type != RADIUS_AUTH: usr.auth_type = RADIUS_AUTH 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.jsonify({ 'redirect': flask.request.url_root[:-1] + key_link['view_url'], }, 202)
def after_request(response): if not flask.g.authed: raise ValueError('Request not authorized') resp_time = int((time.time() - flask.g.start) * 1000) db_time = int(flask.g.query_time * 1000) db_reads = flask.g.query_count db_writes = flask.g.write_count response.headers.add('Execution-Time', resp_time) response.headers.add('Query-Time', db_time) response.headers.add('Query-Count', db_reads) response.headers.add('Write-Count', db_writes) if not flask.request.path.startswith('/event'): monitoring.insert_point('requests', { 'host': settings.local.host.name, }, { 'path': flask.request.path, 'remote_ip': utils.get_remote_addr(), 'response_time': resp_time, 'db_time': db_time, 'db_reads': db_reads, 'db_writes': db_writes, }) return response
def user_post(org_id): if settings.app.demo_mode: return utils.demo_blocked() remote_addr = utils.get_remote_addr() if isinstance(flask.request.json, list): users_data = flask.request.json else: users_data = [flask.request.json] if len(users_data) > 10: if _users_background: return utils.jsonify({ 'error': USERS_BACKGROUND_BUSY, 'error_msg': USERS_BACKGROUND_BUSY_MSG, }, 429) thread = threading.Thread( target=_create_users, args=(org_id, users_data, remote_addr, True), ) thread.daemon = True thread.start() return utils.jsonify({ 'status': USERS_BACKGROUND, 'status_msg': USERS_BACKGROUND_MSG, }, 202) return _create_users(org_id, users_data, remote_addr, False)
def admin_post(): if settings.app.demo_mode: return utils.demo_blocked() if not flask.g.administrator.super_user: return utils.jsonify({"error": REQUIRES_SUPER_USER, "error_msg": REQUIRES_SUPER_USER_MSG}, 400) username = utils.filter_str(flask.request.json["username"]) password = flask.request.json["password"] otp_auth = flask.request.json.get("otp_auth", False) auth_api = flask.request.json.get("auth_api", False) disabled = flask.request.json.get("disabled", False) super_user = flask.request.json.get("super_user", False) try: admin = auth.new_admin( username=username, password=password, default=True, otp_auth=otp_auth, auth_api=auth_api, disabled=disabled, super_user=super_user, ) except pymongo.errors.DuplicateKeyError: return utils.jsonify({"error": ADMIN_USERNAME_EXISTS, "error_msg": ADMIN_USERNAME_EXISTS_MSG}, 400) admin.audit_event("admin_created", "Administrator created", remote_addr=utils.get_remote_addr()) event.Event(type=ADMINS_UPDATED) return utils.jsonify(admin.dict())
def user_linked_key_conf_get(key_id, server_id): doc = _find_doc({ 'key_id': key_id, }) if not doc: return flask.abort(404) org = organization.get_by_id(doc['org_id']) if not org: return flask.abort(404) user = org.get_user(id=doc['user_id']) if not user: return flask.abort(404) if user.disabled: return flask.abort(403) key_conf = user.build_key_conf(server_id) user.audit_event('user_profile', 'User profile downloaded with temporary profile link', remote_addr=utils.get_remote_addr(), ) response = flask.Response(response=key_conf['conf'], mimetype='application/ovpn') response.headers.add('Content-Disposition', 'attachment; filename="%s"' % key_conf['name']) return response
def sso_duo_post(): sso_mode = settings.app.sso token = utils.filter_str(flask.request.json.get("token")) or None passcode = utils.filter_str(flask.request.json.get("passcode")) or None if not token or not passcode: return utils.jsonify({"error": TOKEN_INVALID, "error_msg": TOKEN_INVALID_MSG}, 401) tokens_collection = mongo.get_collection("sso_tokens") doc = tokens_collection.find_one({"_id": token}) if not doc or doc["_id"] != token: return utils.jsonify({"error": TOKEN_INVALID, "error_msg": TOKEN_INVALID_MSG}, 401) username = doc["username"] email = doc["email"] org_id = doc["org_id"] groups = doc["groups"] duo_auth = sso.Duo( username=username, factor=settings.app.sso_duo_mode, remote_ip=utils.get_remote_addr(), auth_type="Key", passcode=passcode, ) valid = duo_auth.authenticate() if not valid: return utils.jsonify({"error": PASSCODE_INVALID, "error_msg": PASSCODE_INVALID_MSG}, 401) org = organization.get_by_id(org_id) if not org: return flask.abort(405) usr = org.find_user(name=username) 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.jsonify({"redirect": utils.get_url_root() + key_link["view_url"]}, 200)
def auth_session_post(): username = flask.request.json["username"] password = flask.request.json["password"] otp_code = flask.request.json.get("otp_code") remote_addr = utils.get_remote_addr() admin = auth.get_by_username(username, remote_addr) if not admin: if settings.app.sso == RADIUS_AUTH: return _auth_radius(username, password) time.sleep(random.randint(0, 100) / 1000.0) return utils.jsonify({"error": AUTH_INVALID, "error_msg": AUTH_INVALID_MSG}, 401) if not otp_code and admin.otp_auth: return utils.jsonify({"error": AUTH_OTP_REQUIRED, "error_msg": AUTH_OTP_REQUIRED_MSG}, 402) if not admin.auth_check(password, otp_code, remote_addr): time.sleep(random.randint(0, 100) / 1000.0) return utils.jsonify({"error": AUTH_INVALID, "error_msg": AUTH_INVALID_MSG}, 401) flask.session["session_id"] = admin.new_session() flask.session["admin_id"] = str(admin.id) flask.session["timestamp"] = int(utils.time_now()) if not settings.conf.ssl: flask.session["source"] = remote_addr return utils.jsonify({"authenticated": True, "default": admin.default or False})
def admin_delete(admin_id): if settings.app.demo_mode: return utils.demo_blocked() if not flask.g.administrator.super_user: return utils.jsonify({ 'error': REQUIRES_SUPER_USER, 'error_msg': REQUIRES_SUPER_USER_MSG, }, 400) admin = auth.get_by_id(admin_id) remote_addr = utils.get_remote_addr() if admin.super_user and auth.super_user_count() < 2: return utils.jsonify({ 'error': NO_ADMINS, 'error_msg': NO_ADMINS_MSG, }, 400) journal.entry( journal.ADMIN_DELETE, admin.journal_data, event_long='Administrator deleted', remote_addr=remote_addr, ) admin.remove() event.Event(type=ADMINS_UPDATED) return utils.jsonify({})
def user_uri_key_page_get(short_code): remote_addr = utils.get_remote_addr() 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']) usr = org.get_user(id=doc['user_id']) if usr.disabled: return flask.abort(403) journal.entry( journal.USER_PROFILE_SUCCESS, usr.journal_data, remote_address=remote_addr, event_long='User temporary profile downloaded from pritunl client', ) usr.audit_event('user_profile', 'User temporary profile downloaded from pritunl client', remote_addr=remote_addr, ) keys = {} for server in usr.iter_servers(): key = usr.build_key_conf(server.id) keys[key['name']] = key['conf'] return utils.jsonify(keys)
def after_request(response): if settings.app.check_requests and not flask.g.valid: raise ValueError("Request not authorized") resp_time = int((time.time() - flask.g.start) * 1000) db_time = int(flask.g.query_time * 1000) db_reads = flask.g.query_count db_writes = flask.g.write_count response.headers.add("Execution-Time", resp_time) response.headers.add("Query-Time", db_time) response.headers.add("Query-Count", db_reads) response.headers.add("Write-Count", db_writes) response.headers.add("X-Frame-Options", "DENY") if not flask.request.path.startswith("/event"): monitoring.insert_point( "requests", {"host": settings.local.host.name}, { "path": flask.request.path, "remote_ip": utils.get_remote_addr(), "response_time": resp_time, "db_time": db_time, "db_reads": db_reads, "db_writes": db_writes, }, ) return response
def user_key_tar_archive_get(org_id, user_id): usr, resp = _get_key_tar_archive(org_id, user_id) usr.audit_event('user_profile', 'User tar profile downloaded from web console', remote_addr=utils.get_remote_addr(), ) return resp
def sso_yubico_post(): remote_addr = utils.get_remote_addr() sso_mode = settings.app.sso token = utils.filter_str(flask.request.json.get('token')) or None key = utils.filter_str(flask.request.json.get('key')) or None if sso_mode not in (GOOGLE_YUBICO_AUTH, SLACK_YUBICO_AUTH, SAML_YUBICO_AUTH, SAML_OKTA_YUBICO_AUTH, SAML_ONELOGIN_YUBICO_AUTH): return flask.abort(404) if not token or not key: return utils.jsonify({ 'error': TOKEN_INVALID, 'error_msg': TOKEN_INVALID_MSG, }, 401) tokens_collection = mongo.get_collection('sso_tokens') doc = tokens_collection.find_and_modify(query={ '_id': token, }, remove=True) if not doc or doc['_id'] != token or doc['type'] != YUBICO_AUTH: journal.entry( journal.SSO_AUTH_FAILURE, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_INVALID_TOKEN, reason_long='Invalid Yubikey authentication token', ) return utils.jsonify({ 'error': TOKEN_INVALID, 'error_msg': TOKEN_INVALID_MSG, }, 401) username = doc['username'] email = doc['email'] org_id = doc['org_id'] groups = set(doc['groups'] or []) valid, yubico_id = sso.auth_yubico(key) if not valid or not yubico_id: journal.entry( journal.SSO_AUTH_FAILURE, username=username, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_YUBIKEY_FAILED, reason_long='Yubikey authentication failed', ) return utils.jsonify({ 'error': YUBIKEY_INVALID, 'error_msg': YUBIKEY_INVALID_MSG, }, 401) return _validate_user(username, email, sso_mode, org_id, groups, remote_addr, yubico_id=yubico_id)
def user_key_link_get(org_id, user_id): org = organization.get_by_id(org_id) usr = org.get_user(user_id) usr.audit_event('user_profile', 'User temporary profile links created from web console', remote_addr=utils.get_remote_addr(), ) return utils.jsonify(org.create_user_key_link(user_id))
def user_key_pin_put(key_id): doc = _find_doc({ 'key_id': key_id, }) if not doc: return flask.abort(404) if settings.app.demo_mode: return utils.demo_blocked() org = organization.get_by_id(doc['org_id']) usr = org.get_user(doc['user_id']) if usr.disabled: return flask.abort(403) if RADIUS_AUTH in usr.auth_type: return utils.jsonify({ 'error': PIN_RADIUS, 'error_msg': PIN_RADIUS_MSG, }, 400) current_pin = utils.filter_str( flask.request.json.get('current_pin')) or None pin = utils.filter_str(flask.request.json.get('pin')) or None if pin: if not pin.isdigit(): return utils.jsonify({ 'error': PIN_NOT_DIGITS, 'error_msg': PIN_NOT_DIGITS_MSG, }, 400) if len(pin) < settings.user.pin_min_length: return utils.jsonify({ 'error': PIN_TOO_SHORT, 'error_msg': PIN_TOO_SHORT_MSG, }, 400) if usr.pin and not usr.check_pin(current_pin): return utils.jsonify({ 'error': PIN_INVALID, 'error_msg': PIN_INVALID_MSG, }, 400) if usr.set_pin(pin): usr.audit_event('user_updated', 'User pin changed with temporary profile link', remote_addr=utils.get_remote_addr(), ) usr.commit() event.Event(type=USERS_UPDATED, resource_id=org.id) return utils.jsonify({})
def user_post(org_id): if settings.app.demo_mode: return utils.demo_blocked() org = organization.get_by_id(org_id) users = [] if isinstance(flask.request.json, list): users_data = flask.request.json else: users_data = [flask.request.json] try: for user_data in users_data: name = utils.filter_str(user_data['name']) email = utils.filter_str(user_data.get('email')) disabled = user_data.get('disabled') network_links = user_data.get('network_links') bypass_secondary = user_data.get('bypass_secondary') dns_servers = user_data.get('dns_servers') or None dns_suffix = utils.filter_str(user_data.get('dns_suffix')) or None user = org.new_user(type=CERT_CLIENT, name=name, email=email, disabled=disabled, bypass_secondary=bypass_secondary, dns_servers=dns_servers, dns_suffix=dns_suffix) user.audit_event('user_created', 'User created from web console', remote_addr=utils.get_remote_addr(), ) if network_links: for network_link in network_links: try: user.add_network_link(network_link) except (ipaddress.AddressValueError, ValueError): return _network_link_invalid() except ServerOnlineError: return utils.jsonify({ 'error': NETWORK_LINK_NOT_OFFLINE, 'error_msg': NETWORK_LINK_NOT_OFFLINE_MSG, }, 400) users.append(user.dict()) finally: event.Event(type=ORGS_UPDATED) event.Event(type=USERS_UPDATED, resource_id=org.id) event.Event(type=SERVERS_UPDATED) if isinstance(flask.request.json, list): logger.LogEntry(message='Created %s new users.' % len( flask.request.json)) return utils.jsonify(users) else: logger.LogEntry(message='Created new user "%s".' % users[0]['name']) return utils.jsonify(users[0])
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 %>', '') 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 admin_post(): if settings.app.demo_mode: return utils.demo_blocked() if not flask.g.administrator.super_user: return utils.jsonify({ 'error': REQUIRES_SUPER_USER, 'error_msg': REQUIRES_SUPER_USER_MSG, }, 400) username = utils.filter_str(flask.request.json['username']).lower() password = flask.request.json['password'] yubikey_id = flask.request.json.get('yubikey_id') or None yubikey_id = yubikey_id[:12] if yubikey_id else None otp_auth = flask.request.json.get('otp_auth', False) auth_api = flask.request.json.get('auth_api', False) disabled = flask.request.json.get('disabled', False) super_user = flask.request.json.get('super_user', False) remote_addr = utils.get_remote_addr() try: admin = auth.new_admin( username=username, password=password, yubikey_id=yubikey_id, default=True, otp_auth=otp_auth, auth_api=auth_api, disabled=disabled, super_user=super_user, ) except pymongo.errors.DuplicateKeyError: return utils.jsonify({ 'error': ADMIN_USERNAME_EXISTS, 'error_msg': ADMIN_USERNAME_EXISTS_MSG, }, 400) admin.audit_event('admin_created', 'Administrator created', remote_addr=remote_addr, ) journal.entry( journal.ADMIN_CREATE, admin.journal_data, event_long='Administrator created', remote_addr=remote_addr, ) event.Event(type=ADMINS_UPDATED) return utils.jsonify(admin.dict())
def auth_token_post(): username = flask.request.json['username'] password = flask.request.json['password'] remote_addr = utils.get_remote_addr() if not utils.check_auth(username, password, remote_addr): return utils.jsonify({ 'error': AUTH_INVALID, 'error_msg': AUTH_INVALID_MSG, }, 401) auth_token = AuthToken() return utils.jsonify({ 'auth_token': auth_token.id, })
def user_linked_key_onc_archive_get(key_id): doc = _find_doc({ 'key_id': key_id, }) if not doc: return flask.abort(404) usr, resp = _get_onc_archive(doc['org_id'], doc['user_id']) usr.audit_event('user_profile', 'User onc profile downloaded with temporary profile link', remote_addr=utils.get_remote_addr(), ) return resp
def user_otp_secret_put(org_id, user_id): if settings.app.demo_mode: return utils.demo_blocked() org = organization.get_by_id(org_id) user = org.get_user(user_id) user.audit_event('user_updated', 'User two step secret reset', remote_addr=utils.get_remote_addr(), ) user.generate_otp_secret() user.commit() event.Event(type=USERS_UPDATED, resource_id=org.id) return utils.jsonify(user.dict())
def user_linked_key_conf_get(org_id, user_id, server_id): org = organization.get_by_id(org_id) usr = org.get_user(user_id) key_conf = usr.build_key_conf(server_id) usr.audit_event('user_profile', 'User key profile downloaded from web console', remote_addr=utils.get_remote_addr(), ) response = flask.Response(response=key_conf['conf'], mimetype='application/ovpn') response.headers.add('Content-Disposition', 'attachment; filename="%s"' % key_conf['name']) return response
def user_linked_key_page_delete(short_code): utils.rand_sleep() remote_addr = utils.get_remote_addr() journal.entry( journal.USER_PROFILE_DELETE, remote_address=remote_addr, event_long='Temporary profile link deleted', ) collection = mongo.get_collection('users_key_link') collection.remove({ 'short_id': short_code, }) return utils.jsonify({})
def user_key_tar_archive_get(org_id, user_id): remote_addr = utils.get_remote_addr() usr, resp = _get_key_tar_archive(org_id, user_id) usr.audit_event('user_profile', 'User tar profile downloaded from web console', remote_addr=remote_addr, ) journal.entry( journal.USER_PROFILE_SUCCESS, usr.journal_data, remote_address=remote_addr, event_long='User tar profile downloaded from web console', ) return resp
def user_otp_secret_put(org_id, user_id): if settings.app.demo_mode: return utils.demo_blocked() org = organization.get_by_id(org_id) user = org.get_user(user_id) user.audit_event( 'user_updated', 'User two step secret reset', remote_addr=utils.get_remote_addr(), ) user.generate_otp_secret() user.commit() event.Event(type=USERS_UPDATED, resource_id=org.id) return utils.jsonify(user.dict())
def user_linked_key_conf_get(org_id, user_id, server_id): org = organization.get_by_id(org_id) usr = org.get_user(user_id) key_conf = usr.build_key_conf(server_id) usr.audit_event( 'user_profile', 'User key profile downloaded from web console', remote_addr=utils.get_remote_addr(), ) response = flask.Response(response=key_conf['conf'], mimetype='application/ovpn') response.headers.add('Content-Disposition', 'attachment; filename="%s"' % key_conf['name']) return response
def user_linked_key_zip_archive_get(key_id): doc = _find_doc({ 'key_id': key_id, }) if not doc: return flask.abort(404) usr, resp = _get_key_zip_archive(doc['org_id'], doc['user_id']) if usr.disabled: return flask.abort(403) usr.audit_event('user_profile', 'User zip profile downloaded with temporary profile link', remote_addr=utils.get_remote_addr(), ) return resp
def user_linked_key_page_delete(short_code): utils.rand_sleep() short_code = short_code[:128] remote_addr = utils.get_remote_addr() journal.entry( journal.USER_PROFILE_DELETE, remote_address=remote_addr, event_long='Temporary profile link deleted', ) collection = mongo.get_collection('users_key_link') collection.remove({ 'short_id': short_code, }) return utils.jsonify({})
def auth_session_post(): username = utils.json_filter_str('username') password = utils.json_str('password') otp_code = utils.json_opt_filter_str('otp_code') yubico_key = utils.json_opt_filter_str('yubico_key') remote_addr = utils.get_remote_addr() time.sleep(random.randint(50, 100) / 1000.) admin = auth.get_by_username(username, remote_addr) if not admin: if settings.app.sso and RADIUS_AUTH in settings.app.sso: return _auth_radius(username, password) time.sleep(random.randint(0, 100) / 1000.) return _auth_plugin(username, password) if (not otp_code and admin.otp_auth) or \ (not yubico_key and admin.yubikey_id): return utils.jsonify({ 'error': AUTH_OTP_REQUIRED, 'error_msg': AUTH_OTP_REQUIRED_MSG, 'otp_auth': admin.otp_auth, 'yubico_auth': bool(admin.yubikey_id), }, 402) if not admin.auth_check(password, otp_code, yubico_key, remote_addr): time.sleep(random.randint(0, 100) / 1000.) return utils.jsonify({ 'error': AUTH_INVALID, 'error_msg': AUTH_INVALID_MSG, }, 401) flask.session['session_id'] = admin.new_session() flask.session['admin_id'] = str(admin.id) flask.session['timestamp'] = int(utils.time_now()) if not settings.app.server_ssl: flask.session['source'] = remote_addr utils.set_flask_sig() return utils.jsonify({ 'authenticated': True, 'default': admin.default or False, })
def user_key_link_get(org_id, user_id): remote_addr = utils.get_remote_addr() org = organization.get_by_id(org_id) usr = org.get_user(user_id) usr.audit_event('user_profile', 'User temporary profile links created from web console', remote_addr=remote_addr, ) journal.entry( journal.USER_PROFILE_SUCCESS, usr.journal_data, remote_address=remote_addr, event_long='User temporary profile links created from web console', ) return utils.jsonify(org.create_user_key_link(user_id))
def admin_post(): if settings.app.demo_mode: return utils.demo_blocked() if not flask.g.administrator.super_user: return utils.jsonify( { 'error': REQUIRES_SUPER_USER, 'error_msg': REQUIRES_SUPER_USER_MSG, }, 400) username = utils.filter_str(flask.request.json['username']).lower() password = flask.request.json['password'] otp_auth = flask.request.json.get('otp_auth', False) auth_api = flask.request.json.get('auth_api', False) disabled = flask.request.json.get('disabled', False) super_user = flask.request.json.get('super_user', False) try: admin = auth.new_admin( username=username, password=password, default=True, otp_auth=otp_auth, auth_api=auth_api, disabled=disabled, super_user=super_user, ) except pymongo.errors.DuplicateKeyError: return utils.jsonify( { 'error': ADMIN_USERNAME_EXISTS, 'error_msg': ADMIN_USERNAME_EXISTS_MSG, }, 400) admin.audit_event( 'admin_created', 'Administrator created', remote_addr=utils.get_remote_addr(), ) event.Event(type=ADMINS_UPDATED) return utils.jsonify(admin.dict())
def after_request(response): if settings.app.check_requests and not flask.g.valid: raise ValueError('Request not authorized') response.headers.add('X-Frame-Options', 'DENY') if settings.app.server_ssl: response.headers.add('Strict-Transport-Security', 'max-age=31536000') if not flask.request.path.startswith('/event'): monitoring.insert_point('requests', { 'host': settings.local.host.name, }, { 'path': flask.request.path, 'remote_ip': utils.get_remote_addr(), 'response_time': int((time.time() - flask.g.start) * 1000), }) return response
def auth_session_post(): username = flask.request.json['username'] password = flask.request.json['password'] otp_code = flask.request.json.get('otp_code') remote_addr = utils.get_remote_addr() admin = auth.get_by_username(username, remote_addr) if not admin: if settings.app.sso == RADIUS_AUTH: return _auth_radius(username, password) time.sleep(random.randint(0, 100) / 1000.) return utils.jsonify( { 'error': AUTH_INVALID, 'error_msg': AUTH_INVALID_MSG, }, 401) if not otp_code and admin.otp_auth: return utils.jsonify( { 'error': AUTH_OTP_REQUIRED, 'error_msg': AUTH_OTP_REQUIRED_MSG, }, 402) if not admin.auth_check(password, otp_code, remote_addr): time.sleep(random.randint(0, 100) / 1000.) return utils.jsonify( { 'error': AUTH_INVALID, 'error_msg': AUTH_INVALID_MSG, }, 401) flask.session['session_id'] = admin.new_session() flask.session['admin_id'] = str(admin.id) flask.session['timestamp'] = int(utils.time_now()) if not settings.conf.ssl: flask.session['source'] = remote_addr return utils.jsonify({ 'authenticated': True, 'default': admin.default or False, })
def auth_delete(): admin_id = utils.session_opt_str('admin_id') session_id = utils.session_opt_str('session_id') remote_addr = utils.get_remote_addr() journal.entry( journal.ADMIN_SESSION_END, admin_id=admin_id, session_id=session_id, remote_address=remote_addr, ) if admin_id and session_id: admin_id = utils.ObjectId(admin_id) auth.clear_session(admin_id, str(session_id)) flask.session.clear() return utils.jsonify({ 'authenticated': False, })
def user_linked_key_onc_archive_get(key_id): key_id = key_id[:128] remote_addr = utils.get_remote_addr() doc = _find_doc({ 'key_id': key_id, }) if not doc: journal.entry( journal.USER_PROFILE_FAILURE, remote_address=remote_addr, event_long='Key ID not found', ) return flask.abort(404) if settings.user.restrict_import: return flask.abort(404) usr, resp = _get_onc_archive(doc['org_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 onc profile downloaded with temporary profile link', ) usr.audit_event( 'user_profile', 'User onc profile downloaded with temporary profile link', remote_addr=remote_addr, ) return resp
def auth_session_post(): username = flask.request.json['username'] password = flask.request.json['password'] remote_addr = utils.get_remote_addr() admin = auth.check_auth(username, password, remote_addr) if not admin: return utils.jsonify({ 'error': AUTH_INVALID, 'error_msg': AUTH_INVALID_MSG, }, 401) flask.session['admin_id'] = str(admin.id) flask.session['timestamp'] = int(time.time()) if not settings.conf.ssl: flask.session['source'] = remote_addr return utils.jsonify({ 'authenticated': True, 'default': admin.default, })
def auth_session_post(): username = flask.request.json['username'] password = flask.request.json['password'] otp_code = flask.request.json.get('otp_code') remote_addr = utils.get_remote_addr() admin = auth.get_by_username(username, remote_addr) if not admin: if settings.app.sso == RADIUS_AUTH: return _auth_radius(username, password) time.sleep(random.randint(0, 100) / 1000.) return utils.jsonify({ 'error': AUTH_INVALID, 'error_msg': AUTH_INVALID_MSG, }, 401) if not otp_code and admin.otp_auth: return utils.jsonify({ 'error': AUTH_OTP_REQUIRED, 'error_msg': AUTH_OTP_REQUIRED_MSG, }, 402) if not admin.auth_check(password, otp_code, remote_addr): time.sleep(random.randint(0, 100) / 1000.) return utils.jsonify({ 'error': AUTH_INVALID, 'error_msg': AUTH_INVALID_MSG, }, 401) flask.session['session_id'] = admin.new_session() flask.session['admin_id'] = str(admin.id) flask.session['timestamp'] = int(utils.time_now()) if not settings.conf.ssl: flask.session['source'] = remote_addr return utils.jsonify({ 'authenticated': True, 'default': admin.default or False, })
def user_uri_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 downloaded from pritunl client', remote_addr=utils.get_remote_addr(), ) keys = {} for server in org.iter_servers(): key = user.build_key_conf(server.id) keys[key['name']] = key['conf'] return utils.jsonify(keys)
def auth_session_post(): username = flask.request.json['username'] password = flask.request.json['password'] remote_addr = utils.get_remote_addr() if not utils.check_auth(username, password, remote_addr): return utils.jsonify({ 'error': AUTH_INVALID, 'error_msg': AUTH_INVALID_MSG, }, 401) flask.session['auth'] = True flask.session['timestamp'] = int(time.time()) if not app_server.ssl: flask.session['source'] = remote_addr data = { 'authenticated': True, } if password == DEFAULT_PASSWORD: data['default_password'] = True return utils.jsonify(data)
def user_uri_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 downloaded from pritunl client', remote_addr=utils.get_remote_addr(), ) keys = {} for server in org.iter_servers(): key = user.build_key_conf(server.id) keys[key['name']] = key['conf'] return utils.jsonify(keys)
def user_linked_key_conf_get(key_id, server_id): doc = _find_doc({ 'key_id': key_id, }) if not doc: return flask.abort(404) org = organization.get_by_id(doc['org_id']) user = org.get_user(id=doc['user_id']) key_conf = user.build_key_conf(server_id) user.audit_event( 'user_profile', 'User profile downloaded with temporary profile link', remote_addr=utils.get_remote_addr(), ) response = flask.Response(response=key_conf['conf'], mimetype='application/ovpn') response.headers.add('Content-Disposition', 'attachment; filename="%s"' % key_conf['name']) return response
def after_request(response): resp_time = int((time.time() - flask.g.start) * 1000) db_time = int(flask.g.query_time * 1000) db_reads = flask.g.query_count db_writes = flask.g.write_count response.headers.add('Execution-Time', resp_time) response.headers.add('Query-Time', db_time) response.headers.add('Query-Count', db_reads) response.headers.add('Write-Count', db_writes) if not flask.request.path.startswith('/event'): monitoring.insert_point('requests', { 'host': settings.local.host.name, }, { 'path': flask.request.path, 'remote_ip': utils.get_remote_addr(), 'response_time': resp_time, 'db_time': db_time, 'db_reads': db_reads, 'db_writes': db_writes, }) return response
def sso_duo_post(): sso_mode = settings.app.sso token = utils.filter_str(flask.request.json.get('token')) or None passcode = utils.filter_str(flask.request.json.get('passcode')) or '' if sso_mode not in (DUO_AUTH, AZURE_DUO_AUTH, GOOGLE_DUO_AUTH, SLACK_DUO_AUTH, SAML_DUO_AUTH, SAML_OKTA_DUO_AUTH, SAML_ONELOGIN_DUO_AUTH, RADIUS_DUO_AUTH): return flask.abort(404) if not token: return utils.jsonify( { 'error': TOKEN_INVALID, 'error_msg': TOKEN_INVALID_MSG, }, 401) tokens_collection = mongo.get_collection('sso_tokens') doc = tokens_collection.find_and_modify(query={ '_id': token, }, remove=True) if not doc or doc['_id'] != token or doc['type'] != DUO_AUTH: return utils.jsonify( { 'error': TOKEN_INVALID, 'error_msg': TOKEN_INVALID_MSG, }, 401) username = doc['username'] email = doc['email'] org_id = doc['org_id'] groups = set(doc['groups'] or []) if settings.app.sso_duo_mode == 'passcode': duo_auth = sso.Duo( username=username, factor=settings.app.sso_duo_mode, remote_ip=utils.get_remote_addr(), auth_type='Key', passcode=passcode, ) valid = duo_auth.authenticate() if not valid: logger.warning( 'Duo authentication not valid', 'sso', username=username, ) return utils.jsonify( { 'error': PASSCODE_INVALID, 'error_msg': PASSCODE_INVALID_MSG, }, 401) else: duo_auth = sso.Duo( username=username, factor=settings.app.sso_duo_mode, remote_ip=utils.get_remote_addr(), auth_type='Key', ) valid = duo_auth.authenticate() if not valid: logger.warning( 'Duo authentication not valid', 'sso', username=username, ) return utils.jsonify( { 'error': DUO_FAILED, 'error_msg': DUO_FAILED_MSG, }, 401) valid, org_id_new, groups2 = sso.plugin_sso_authenticate( sso_type='duo', user_name=username, user_email=email, remote_ip=utils.get_remote_addr(), ) if valid: org_id = org_id_new or org_id else: logger.warning( 'Duo plugin authentication not valid', 'sso', username=username, ) return flask.abort(401) groups = groups | set(groups2 or []) return _validate_user(username, email, sso_mode, org_id, groups)
def user_put(org_id, user_id): if settings.app.demo_mode: return utils.demo_blocked() org = organization.get_by_id(org_id) user = org.get_user(user_id) reset_user = False port_forwarding_event = False if 'name' in flask.request.json: name = utils.filter_str(flask.request.json['name']) or None if name != user.name: user.audit_event( 'user_updated', 'User name changed', remote_addr=utils.get_remote_addr(), ) user.name = name if 'email' in flask.request.json: email = utils.filter_str(flask.request.json['email']) or None if email != user.email: user.audit_event( 'user_updated', 'User email changed', remote_addr=utils.get_remote_addr(), ) user.email = email if 'groups' in flask.request.json: groups = flask.request.json['groups'] or [] for i, group in enumerate(groups): groups[i] = utils.filter_str(group) groups = set(groups) if groups != set(user.groups or []): user.audit_event( 'user_updated', 'User groups changed', remote_addr=utils.get_remote_addr(), ) user.groups = list(groups) if 'pin' in flask.request.json: pin = flask.request.json['pin'] if pin is not True: if pin: if settings.user.pin_mode == PIN_DISABLED: return utils.jsonify( { 'error': PIN_IS_DISABLED, 'error_msg': PIN_IS_DISABLED_MSG, }, 400) if RADIUS_AUTH in user.auth_type: return utils.jsonify( { 'error': PIN_RADIUS, 'error_msg': PIN_RADIUS_MSG, }, 400) if not pin.isdigit(): return utils.jsonify( { 'error': PIN_NOT_DIGITS, 'error_msg': PIN_NOT_DIGITS_MSG, }, 400) if len(pin) < settings.user.pin_min_length: return utils.jsonify( { 'error': PIN_TOO_SHORT, 'error_msg': PIN_TOO_SHORT_MSG, }, 400) if user.set_pin(pin): user.audit_event( 'user_updated', 'User pin changed', remote_addr=utils.get_remote_addr(), ) if 'network_links' in flask.request.json: network_links_cur = set(user.get_network_links()) network_links_new = set() for network_link in flask.request.json['network_links']: try: network_link = str(ipaddress.IPNetwork(network_link)) except (ipaddress.AddressValueError, ValueError): return _network_link_invalid() network_links_new.add(network_link) network_links_add = network_links_new - network_links_cur network_links_rem = network_links_cur - network_links_new if len(network_links_add) or len(network_links_rem): reset_user = True user.audit_event( 'user_updated', 'User network links updated', remote_addr=utils.get_remote_addr(), ) try: for network_link in network_links_add: user.add_network_link(network_link) except ServerOnlineError: return utils.jsonify( { 'error': NETWORK_LINK_NOT_OFFLINE, 'error_msg': NETWORK_LINK_NOT_OFFLINE_MSG, }, 400) for network_link in network_links_rem: user.remove_network_link(network_link) if 'port_forwarding' in flask.request.json: port_forwarding = [] for data in flask.request.json['port_forwarding'] or []: port_forwarding.append({ 'protocol': utils.filter_str(data.get('protocol')), 'port': utils.filter_str(data.get('port')), 'dport': utils.filter_str(data.get('dport')), }) if port_forwarding != user.port_forwarding: port_forwarding_event = True user.audit_event( 'user_updated', 'User port forwarding changed', remote_addr=utils.get_remote_addr(), ) user.port_forwarding = port_forwarding disabled = flask.request.json.get('disabled') if disabled is not None: if disabled != user.disabled: user.audit_event( 'user_updated', 'User %s' % ('disabled' if disabled else 'enabled'), remote_addr=utils.get_remote_addr(), ) user.disabled = disabled bypass_secondary = flask.request.json.get('bypass_secondary') if bypass_secondary is not None: user.bypass_secondary = True if bypass_secondary else False client_to_client = flask.request.json.get('client_to_client') if client_to_client is not None: user.client_to_client = True if client_to_client else False if 'dns_servers' in flask.request.json: dns_servers = flask.request.json['dns_servers'] or None if user.dns_servers != dns_servers: user.audit_event( 'user_updated', 'User dns servers changed', remote_addr=utils.get_remote_addr(), ) reset_user = True user.dns_servers = dns_servers if 'dns_suffix' in flask.request.json: dns_suffix = utils.filter_str(flask.request.json['dns_suffix']) or None if user.dns_suffix != dns_suffix: user.audit_event( 'user_updated', 'User dns suffix changed', remote_addr=utils.get_remote_addr(), ) reset_user = True user.dns_suffix = dns_suffix user.commit() event.Event(type=USERS_UPDATED, resource_id=user.org.id) if port_forwarding_event: messenger.publish('port_forwarding', { 'org_id': org.id, 'user_id': user.id, }) if reset_user or disabled: user.disconnect() if disabled: if user.type == CERT_CLIENT: logger.LogEntry(message='Disabled user "%s".' % user.name) elif disabled == False and user.type == CERT_CLIENT: logger.LogEntry(message='Enabled user "%s".' % user.name) send_key_email = flask.request.json.get('send_key_email') if send_key_email and user.email: user.audit_event( 'user_emailed', 'User key email sent to "%s"' % user.email, remote_addr=utils.get_remote_addr(), ) try: user.send_key_email(utils.get_url_root()) except EmailNotConfiguredError: return utils.jsonify( { 'error': EMAIL_NOT_CONFIGURED, 'error_msg': EMAIL_NOT_CONFIGURED_MSG, }, 400) except EmailFromInvalid: return utils.jsonify( { 'error': EMAIL_FROM_INVALID, 'error_msg': EMAIL_FROM_INVALID_MSG, }, 400) except EmailAuthInvalid: return utils.jsonify( { 'error': EMAIL_AUTH_INVALID, 'error_msg': EMAIL_AUTH_INVALID_MSG, }, 400) return utils.jsonify(user.dict())
def _auth_radius(username, password): sso_mode = settings.app.sso valid, org_names, groups = sso.verify_radius(username, password) if not valid: return utils.jsonify({ 'error': AUTH_INVALID, 'error_msg': AUTH_INVALID_MSG, }, 401) org_id = settings.app.sso_org if org_names: 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, groups2 = sso.plugin_sso_authenticate( sso_type='radius', user_name=username, user_email=None, remote_ip=utils.get_remote_addr(), ) if valid: org_id = org_id_new or org_id else: logger.error('Radius plugin authentication not valid', 'sso', username=username, ) return utils.jsonify({ 'error': AUTH_INVALID, 'error_msg': AUTH_INVALID_MSG, }, 401) groups = ((groups or set()) | (groups2 or set())) or None if DUO_AUTH in sso_mode: try: duo_auth = sso.Duo( username=username, factor=settings.app.sso_duo_mode, remote_ip=utils.get_remote_addr(), auth_type='Key', ) valid = duo_auth.authenticate() except InvalidUser: logger.error('Duo authentication username not valid', 'sso', username=username, ) return utils.jsonify({ 'error': AUTH_INVALID, 'error_msg': AUTH_INVALID_MSG, }, 401) if valid: valid, org_id_new, groups2 = sso.plugin_sso_authenticate( sso_type='duo', user_name=username, user_email=None, remote_ip=utils.get_remote_addr(), ) if valid: org_id = org_id_new or org_id else: logger.error('Duo plugin authentication not valid', 'sso', username=username, ) return utils.jsonify({ 'error': AUTH_INVALID, 'error_msg': AUTH_INVALID_MSG, }, 401) groups = ((groups or set()) | (groups2 or set())) or None else: logger.error('Duo authentication not valid', 'sso', username=username, ) return utils.jsonify({ 'error': AUTH_INVALID, 'error_msg': AUTH_INVALID_MSG, }, 401) groups = ((groups or set()) | (groups2 or set())) or None org = organization.get_by_id(org_id) if not org: return flask.abort(405) usr = org.find_user(name=username) if not usr: usr = org.new_user(name=username, 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 utils.jsonify({ 'error': AUTH_DISABLED, 'error_msg': AUTH_DISABLED_MSG, }, 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.set_pin(None) usr.commit(('auth_type', 'pin')) 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.jsonify({ 'redirect': utils.get_url_root() + key_link['view_url'], }, 202)
def _auth_plugin(username, password): if settings.local.sub_plan != 'enterprise': return utils.jsonify({ 'error': AUTH_INVALID, 'error_msg': AUTH_INVALID_MSG, }, 401) valid, org_id, groups = sso.plugin_login_authenticate( user_name=username, password=password, remote_ip=utils.get_remote_addr(), ) if not valid: return utils.jsonify({ 'error': AUTH_INVALID, 'error_msg': AUTH_INVALID_MSG, }, 401) if not org_id: logger.error( 'Login plugin did not return valid organization name', 'auth', org_name=org_id, user_name=username, ) return utils.jsonify({ 'error': AUTH_INVALID, 'error_msg': AUTH_INVALID_MSG, }, 401) org = organization.get_by_id(org_id) if not org: return flask.abort(405) usr = org.find_user(name=username) if not usr: usr = org.new_user(name=username, type=CERT_CLIENT, auth_type=PLUGIN_AUTH, groups=list(groups) if groups else None) usr.audit_event( 'user_created', 'User created with plugin authentication', 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 utils.jsonify({ 'error': AUTH_DISABLED, 'error_msg': AUTH_DISABLED_MSG, }, 403) if groups and groups - set(usr.groups or []): usr.groups = list(set(usr.groups or []) | groups) usr.commit('groups') if usr.auth_type != PLUGIN_AUTH: usr.auth_type = PLUGIN_AUTH usr.set_pin(None) usr.commit(('auth_type', 'pin')) key_link = org.create_user_key_link(usr.id, one_time=True) usr.audit_event('user_profile', 'User profile viewed from plugin authentication', remote_addr=utils.get_remote_addr(), ) return utils.jsonify({ 'redirect': utils.get_url_root() + key_link['view_url'], }, 202)
def auth_user_post(): if settings.app.demo_mode: return utils.demo_blocked() auth_token = flask.request.headers.get('Auth-Token', None) auth_timestamp = flask.request.headers.get('Auth-Timestamp', None) auth_nonce = flask.request.headers.get('Auth-Nonce', None) auth_signature = flask.request.headers.get('Auth-Signature', None) if not auth_token or not auth_timestamp or not auth_nonce or \ not auth_signature: return utils.jsonify( { 'error': AUTH_INVALID, 'error_msg': AUTH_INVALID_MSG, }, 401) auth_nonce = auth_nonce[:32] try: if abs(int(auth_timestamp) - int(utils.time_now())) > \ settings.app.auth_time_window: return utils.jsonify( { 'error': AUTH_INVALID, 'error_msg': AUTH_INVALID_MSG, }, 401) except ValueError: return utils.jsonify( { 'error': AUTH_INVALID, 'error_msg': AUTH_INVALID_MSG, }, 401) org = organization.get_by_token(auth_token) if not org: return utils.jsonify( { 'error': AUTH_INVALID, 'error_msg': AUTH_INVALID_MSG, }, 401) auth_string = '&'.join([ auth_token, auth_timestamp, auth_nonce, flask.request.method, flask.request.path, ] + ([flask.request.data] if flask.request.data else [])) if len(auth_string) > AUTH_SIG_STRING_MAX_LEN: return utils.jsonify( { 'error': AUTH_INVALID, 'error_msg': AUTH_INVALID_MSG, }, 401) if not org.auth_secret or len(org.auth_secret) < 8: return utils.jsonify( { 'error': AUTH_INVALID, 'error_msg': AUTH_INVALID_MSG, }, 401) auth_test_signature = base64.b64encode( hmac.new(org.auth_secret.encode(), auth_string, hashlib.sha256).digest()) if auth_signature != auth_test_signature: return utils.jsonify( { 'error': AUTH_INVALID, 'error_msg': AUTH_INVALID_MSG, }, 401) try: org.nonces_collection.insert({ 'token': auth_token, 'nonce': auth_nonce, 'timestamp': utils.now(), }) except pymongo.errors.DuplicateKeyError: return utils.jsonify( { 'error': AUTH_INVALID, 'error_msg': AUTH_INVALID_MSG, }, 401) username = flask.request.json['username'] network_links = flask.request.json.get('network_links') usr = org.find_user(name=username) if usr: usr.remove() usr = org.new_user(name=username, type=CERT_CLIENT) usr.audit_event('user_created', 'User created with authentication token', remote_addr=utils.get_remote_addr()) if network_links: for network_link in network_links: try: usr.add_network_link(network_link, force=True) except (ipaddress.AddressValueError, ValueError): return _network_link_invalid() event.Event(type=ORGS_UPDATED) event.Event(type=USERS_UPDATED, resource_id=org.id) event.Event(type=SERVERS_UPDATED) keys = {} for svr in org.iter_servers(): key = usr.build_key_conf(svr.id) keys[key['name']] = key['conf'] return utils.jsonify(keys)
def sso_authenticate_post(): if settings.app.sso != DUO_AUTH: return flask.abort(405) username = flask.request.json['username'] usernames = [username] email = None if '@' in username: email = username usernames.append(username.split('@')[0]) valid = False for i, username in enumerate(usernames): try: valid, org_id = sso.auth_duo( username, strong=True, ipaddr=flask.request.remote_addr, type='Key', ) break except InvalidUser: if i == len(usernames) - 1: logger.error('Invalid duo username', 'sso', username=username, ) if not valid: logger.error('Invalid duo username', 'sso', username=username, ) return flask.abort(401) if not org_id: org_id = settings.app.sso_org org = organization.get_by_id(org_id) if not org: logger.error('Organization for Duo sso does not exist', 'sso', org_id=org_id, ) return flask.abort(405) usr = org.find_user(name=username) if not usr: usr = org.new_user(name=username, email=email, type=CERT_CLIENT, auth_type=DUO_AUTH) 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 usr.auth_type != DUO_AUTH: usr.auth_type = DUO_AUTH usr.commit('auth_type') event.Event(type=USERS_UPDATED, resource_id=org.id) 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.get_url_root()[:-1] + key_link['view_url']
def key_sync_get(org_id, user_id, server_id, key_hash): utils.rand_sleep() if not settings.local.sub_active: return utils.response('', status_code=480) auth_token = flask.request.headers.get('Auth-Token', None) auth_timestamp = flask.request.headers.get('Auth-Timestamp', None) auth_nonce = flask.request.headers.get('Auth-Nonce', None) auth_signature = flask.request.headers.get('Auth-Signature', None) if not auth_token or not auth_timestamp or not auth_nonce or \ not auth_signature: return flask.abort(401) auth_nonce = auth_nonce[:32] try: if abs(int(auth_timestamp) - int(utils.time_now())) > \ settings.app.auth_time_window: return flask.abort(401) except ValueError: return flask.abort(401) org = organization.get_by_id(org_id) if not org: return flask.abort(401) user = org.get_user(id=user_id) if not user: return flask.abort(401) elif not user.sync_secret: return flask.abort(401) if user.disabled: return flask.abort(403) auth_string = '&'.join([ user.sync_token, auth_timestamp, auth_nonce, flask.request.method, flask.request.path] + ([flask.request.data] if flask.request.data else [])) if len(auth_string) > AUTH_SIG_STRING_MAX_LEN: return flask.abort(401) auth_test_signature = base64.b64encode(hmac.new( user.sync_secret.encode(), auth_string, hashlib.sha256).digest()) if auth_signature != auth_test_signature: return flask.abort(401) nonces_collection = mongo.get_collection('auth_nonces') try: nonces_collection.insert({ 'token': auth_token, 'nonce': auth_nonce, 'timestamp': utils.now(), }, w=0) except pymongo.errors.DuplicateKeyError: return flask.abort(401) key_conf = user.sync_conf(server_id, key_hash) if key_conf: user.audit_event('user_profile', 'User profile synced from pritunl client', remote_addr=utils.get_remote_addr(), ) return utils.response(key_conf['conf']) return utils.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 = (utils.get_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 user_key_pin_put(key_id): if settings.app.demo_mode: return utils.demo_blocked() doc = _find_doc({ 'key_id': key_id, }) if not doc: return flask.abort(404) if settings.app.demo_mode: return utils.demo_blocked() if settings.user.pin_mode == PIN_DISABLED: return utils.jsonify({ 'error': PIN_IS_DISABLED, 'error_msg': PIN_IS_DISABLED_MSG, }, 400) org = organization.get_by_id(doc['org_id']) usr = org.get_user(doc['user_id']) if usr.disabled: return flask.abort(403) if RADIUS_AUTH in usr.auth_type: return utils.jsonify({ 'error': PIN_RADIUS, 'error_msg': PIN_RADIUS_MSG, }, 400) current_pin = utils.filter_str( flask.request.json.get('current_pin')) or None pin = utils.filter_str(flask.request.json.get('pin')) or None if pin: if not pin.isdigit(): return utils.jsonify({ 'error': PIN_NOT_DIGITS, 'error_msg': PIN_NOT_DIGITS_MSG, }, 400) if len(pin) < settings.user.pin_min_length: return utils.jsonify({ 'error': PIN_TOO_SHORT, 'error_msg': PIN_TOO_SHORT_MSG, }, 400) if usr.pin and not usr.check_pin(current_pin): return utils.jsonify({ 'error': PIN_INVALID, 'error_msg': PIN_INVALID_MSG, }, 400) if usr.set_pin(pin): usr.audit_event('user_updated', 'User pin changed with temporary profile link', remote_addr=utils.get_remote_addr(), ) usr.commit() event.Event(type=USERS_UPDATED, resource_id=org.id) return utils.jsonify({})
def check_session(): auth_token = flask.request.headers.get('Auth-Token', None) if auth_token: auth_timestamp = flask.request.headers.get('Auth-Timestamp', None) auth_nonce = flask.request.headers.get('Auth-Nonce', None) auth_signature = flask.request.headers.get('Auth-Signature', None) if not auth_token or not auth_timestamp or not auth_nonce or \ not auth_signature: return False auth_nonce = auth_nonce[:32] try: if abs(int(auth_timestamp) - int(utils.time_now())) > \ settings.app.auth_time_window: return False except ValueError: return False administrator = find_user(token=auth_token) if not administrator: return False auth_string = '&'.join([ auth_token, auth_timestamp, auth_nonce, flask.request.method, flask.request.path ] + ([flask.request.data] if flask.request.data else [])) if len(auth_string) > AUTH_SIG_STRING_MAX_LEN: return False auth_test_signature = base64.b64encode( hmac.new(administrator.secret.encode(), auth_string, hashlib.sha256).digest()) if auth_signature != auth_test_signature: return False try: Administrator.nonces_collection.insert( { 'token': auth_token, 'nonce': auth_nonce, 'timestamp': utils.now(), }, w=0) except pymongo.errors.DuplicateKeyError: return False else: if not flask.session: return False admin_id = flask.session.get('admin_id') if not admin_id: return False admin_id = bson.ObjectId(admin_id) session_id = flask.session.get('session_id') administrator = get_user(admin_id, session_id) if not administrator: return False if not settings.conf.ssl and flask.session.get( 'source') != utils.get_remote_addr(): flask.session.clear() return False session_timeout = settings.app.session_timeout if session_timeout and int(utils.time_now()) - \ flask.session['timestamp'] > session_timeout: flask.session.clear() return False flask.session['timestamp'] = int(utils.time_now()) flask.g.administrator = administrator return True
def key_sync_get(org_id, user_id, server_id, key_hash): remote_addr = utils.get_remote_addr() if not settings.user.conf_sync: return utils.jsonify({}) if not settings.local.sub_active: return utils.jsonify({}, status_code=480) utils.rand_sleep() auth_token = flask.request.headers.get('Auth-Token', None) auth_timestamp = flask.request.headers.get('Auth-Timestamp', None) auth_nonce = flask.request.headers.get('Auth-Nonce', None) auth_signature = flask.request.headers.get('Auth-Signature', None) if not auth_token or not auth_timestamp or not auth_nonce or \ not auth_signature: journal.entry( journal.USER_SYNC_FAILURE, remote_address=remote_addr, event_long='Missing auth header', ) return flask.abort(406) auth_nonce = auth_nonce[:32] try: if abs(int(auth_timestamp) - int(utils.time_now())) > \ settings.app.auth_time_window: journal.entry( journal.USER_SYNC_FAILURE, remote_address=remote_addr, event_long='Expired auth timestamp', ) return flask.abort(408) except ValueError: journal.entry( journal.USER_SYNC_FAILURE, remote_address=remote_addr, event_long='Invalid auth timestamp', ) return flask.abort(405) org = organization.get_by_id(org_id) if not org: journal.entry( journal.USER_SYNC_FAILURE, remote_address=remote_addr, event_long='Organization not found', ) return flask.abort(404) usr = org.get_user(id=user_id) if not usr: journal.entry( journal.USER_SYNC_FAILURE, remote_address=remote_addr, event_long='User not found', ) return flask.abort(404) elif not usr.sync_secret: journal.entry( journal.USER_SYNC_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='User missing sync secret', ) return flask.abort(410) if auth_token != usr.sync_token: journal.entry( journal.USER_SYNC_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Sync token mismatch', ) return flask.abort(410) if usr.disabled: journal.entry( journal.USER_SYNC_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='User disabled', ) return flask.abort(403) auth_string = '&'.join([ usr.sync_token, auth_timestamp, auth_nonce, flask.request.method, flask.request.path ]) if len(auth_string) > AUTH_SIG_STRING_MAX_LEN: journal.entry( journal.USER_SYNC_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Auth string len limit exceeded', ) return flask.abort(413) auth_test_signature = base64.b64encode( hmac.new(usr.sync_secret.encode(), auth_string, hashlib.sha512).digest()) if not utils.const_compare(auth_signature, auth_test_signature): journal.entry( journal.USER_SYNC_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Sync signature mismatch', ) return flask.abort(401) nonces_collection = mongo.get_collection('auth_nonces') try: nonces_collection.insert({ 'token': auth_token, 'nonce': auth_nonce, 'timestamp': utils.now(), }) except pymongo.errors.DuplicateKeyError: journal.entry( journal.USER_SYNC_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Duplicate key', ) return flask.abort(409) key_conf = usr.sync_conf(server_id, key_hash) if key_conf: usr.audit_event( 'user_profile', 'User profile synced from pritunl client', remote_addr=remote_addr, ) journal.entry( journal.USER_SYNC_SUCCESS, usr.journal_data, remote_address=remote_addr, event_long='User profile synced from pritunl client', ) sync_signature = base64.b64encode( hmac.new(usr.sync_secret.encode(), key_conf['conf'], hashlib.sha512).digest()) return utils.jsonify({ 'signature': sync_signature, 'conf': key_conf['conf'], }) return utils.jsonify({})
def sso_callback_get(): sso_mode = settings.app.sso if sso_mode not in (GOOGLE_AUTH, GOOGLE_DUO_AUTH, SLACK_AUTH, SLACK_DUO_AUTH, SAML_AUTH, SAML_DUO_AUTH, SAML_OKTA_AUTH, SAML_OKTA_DUO_AUTH, SAML_ONELOGIN_AUTH, SAML_ONELOGIN_DUO_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 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) valid, org_name = sso.verify_saml(username, email, org_name) if not valid: return flask.abort(401) org_id = settings.app.sso_org if org_name: org = organization.get_by_name(org_name, fields=('_id')) if org: org_id = org.id 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, org_name = sso.verify_slack(username, user_team, org_names) if not valid: return flask.abort(401) if org_name: org_names = [org_name] 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 else: username = params.get('username')[0] email = username valid, org_name = sso.verify_google(username) if not valid: return flask.abort(401) org_id = settings.app.sso_org if org_name: org = organization.get_by_name(org_name, fields=('_id')) if org: org_id = org.id if DUO_AUTH in sso_mode: valid, _ = sso.auth_duo( username, ipaddr=flask.request.remote_addr, type='Key', ) if not valid: return flask.abort(401) org = organization.get_by_id(org_id) if not org: return flask.abort(405) usr = org.find_user(name=username) if not usr: usr = org.new_user(name=username, email=email, type=CERT_CLIENT, auth_type=sso_mode) 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 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()[:-1] + key_link['view_url'])
def user_linked_key_conf_get(key_id, server_id): remote_addr = utils.get_remote_addr() doc = _find_doc({ 'key_id': key_id, }) if not doc: journal.entry( journal.USER_PROFILE_FAILURE, remote_address=remote_addr, event_long='Key ID not found', ) return flask.abort(404) if settings.user.restrict_import: return flask.abort(404) org = organization.get_by_id(doc['org_id']) if not org: journal.entry( journal.USER_PROFILE_FAILURE, remote_address=remote_addr, event_long='Organization not found', ) return flask.abort(404) usr = org.get_user(id=doc['user_id']) if not usr: journal.entry( journal.USER_PROFILE_FAILURE, remote_address=remote_addr, event_long='User not found', ) return flask.abort(404) if usr.disabled: journal.entry( journal.USER_PROFILE_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='User disabled', ) return flask.abort(403) key_conf = usr.build_key_conf(server_id) journal.entry( journal.USER_PROFILE_SUCCESS, usr.journal_data, remote_address=remote_addr, event_long='User profile downloaded with temporary profile link', ) usr.audit_event( 'user_profile', 'User profile downloaded with temporary profile link', remote_addr=remote_addr, ) response = flask.Response(response=key_conf['conf'], mimetype='application/ovpn') response.headers.add('Content-Disposition', 'attachment; filename="%s"' % key_conf['name']) return response
def user_linked_key_page_get(short_code): 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