def iter_hosts_dict(page=None): clients_collection = mongo.get_collection("clients") server_collection = mongo.get_collection("servers") response = server_collection.aggregate( [ {"$project": {"hosts": True, "organizations": True}}, {"$unwind": "$hosts"}, {"$unwind": "$organizations"}, {"$group": {"_id": "$hosts", "organizations": {"$addToSet": "$organizations"}}}, ] ) orgs = set() host_orgs = collections.defaultdict(list) for doc in response: orgs = orgs.union(doc["organizations"]) host_orgs[doc["_id"]] = doc["organizations"] org_user_count = organization.get_user_count(orgs) for hst in iter_hosts(page=page): users_online = len(clients_collection.distinct("user_id", {"host_id": hst.id, "type": CERT_CLIENT})) user_count = 0 for org_id in host_orgs[hst.id]: user_count += org_user_count.get(org_id, 0) hst.user_count = user_count hst.users_online = users_online yield hst.dict()
def remove(self): user_collection = mongo.get_collection('users') server_collection = mongo.get_collection('servers') logger.debug('Remove org', 'organization', org_id=self.id, ) server_ids = [] for server in self.iter_servers(): server_ids.append(server.id) if server.status == ONLINE: server.stop() server_collection.update({ 'organizations': self.id, }, {'$pull': { 'organizations': self.id, }}) mongo.MongoObject.remove(self) user_collection.remove({ 'org_id': self.id, }) return server_ids
def _find_doc(query, one_time=None, one_time_new=False): utils.rand_sleep() collection = mongo.get_collection("users_key_link") doc = collection.find_one(query) if one_time and doc and doc.get("one_time"): short_id = utils.generate_short_id() collection = mongo.get_collection("users_key_link") if one_time_new: set_doc = {"short_id": short_id} else: set_doc = {"one_time": "used"} response = collection.update( {"_id": doc["_id"], "short_id": doc["short_id"], "one_time": True}, {"$set": set_doc} ) if not response["updatedExisting"]: return None if one_time_new: doc["short_id"] = short_id if not doc: time.sleep(settings.app.rate_limit_sleep) return doc
def remove(self): user_collection = mongo.get_collection('users') user_audit_collection = mongo.get_collection('users_audit') user_net_link_collection = mongo.get_collection('users_net_link') server_collection = mongo.get_collection('servers') user_audit_collection.remove({ 'org_id': self.id, }) user_net_link_collection.remove({ 'org_id': self.id, }) server_ids = [] for server in self.iter_servers(): server_ids.append(server.id) if server.status == ONLINE: server.stop() server_collection.update({ 'organizations': self.id, }, {'$pull': { 'organizations': self.id, }}) mongo.MongoObject.remove(self) user_collection.remove({ 'org_id': self.id, }) return server_ids
def fill_dh_params(): collection = mongo.get_collection('dh_params') queue_collection = mongo.get_collection('queue') new_dh_params = [] dh_param_bits_pool = settings.app.dh_param_bits_pool dh_param_counts = utils.LeastCommonCounter() for dh_param_bits in dh_param_bits_pool: pool_count = collection.find({ 'dh_param_bits': dh_param_bits, }, { '_id': True }).count() dh_param_counts[dh_param_bits] = pool_count pool_count = queue_collection.find({ 'type': 'dh_params', 'dh_param_bits': dh_param_bits, }, { '_id': True }).count() dh_param_counts[dh_param_bits] += pool_count for dh_param_bits, count in dh_param_counts.least_common(): new_dh_params.append([dh_param_bits] * ( settings.app.server_pool_size - count)) for dh_param_bits in utils.roundrobin(*new_dh_params): que = queue.start('dh_params', dh_param_bits=dh_param_bits, priority=LOW)
def _find_doc(query, one_time=None, one_time_new=False): utils.rand_sleep() collection = mongo.get_collection('users_key_link') doc = collection.find_one(query) if one_time and doc and doc.get('one_time'): short_id = utils.generate_short_id() collection = mongo.get_collection('users_key_link') if one_time_new: set_doc = { 'short_id': short_id, } else: set_doc = { 'one_time': 'used', } response = collection.update({ '_id': doc['_id'], 'short_id': doc['short_id'], 'one_time': True, }, {'$set': set_doc}) if not response['updatedExisting']: return None if one_time_new: doc['short_id'] = short_id if not doc: time.sleep(settings.app.rate_limit_sleep) return doc
def fill_user(): collection = mongo.get_collection("users") org_collection = mongo.get_collection("organizations") queue_collection = mongo.get_collection("queue") orgs = {} orgs_count = utils.LeastCommonCounter() type_to_size = {CERT_CLIENT_POOL: settings.app.user_pool_size, CERT_SERVER_POOL: settings.app.server_user_pool_size} for org in organization.iter_orgs(type=None): orgs[org.id] = org orgs_count[org.id, CERT_CLIENT_POOL] = 0 orgs_count[org.id, CERT_SERVER_POOL] = 0 pools = collection.aggregate( [ {"$match": {"type": {"$in": (CERT_CLIENT_POOL, CERT_SERVER_POOL)}}}, {"$project": {"org_id": True, "type": True}}, {"$group": {"_id": {"org_id": "$org_id", "type": "$type"}, "count": {"$sum": 1}}}, ] ) for pool in pools: orgs_count[pool["_id"]["org_id"], pool["_id"]["type"]] += pool["count"] pools = queue_collection.aggregate( [ {"$match": {"type": "init_user_pooled", "user_doc.type": {"$in": (CERT_CLIENT_POOL, CERT_SERVER_POOL)}}}, {"$project": {"user_doc.org_id": True, "user_doc.type": True}}, {"$group": {"_id": {"org_id": "$user_doc.org_id", "type": "$user_doc.type"}, "count": {"$sum": 1}}}, ] ) for pool in pools: orgs_count[pool["_id"]["org_id"], pool["_id"]["type"]] += pool["count"] new_users = [] for org_id_user_type, count in orgs_count.least_common(): org_id, user_type = org_id_user_type pool_size = type_to_size[user_type] if count >= pool_size: break org = orgs.get(org_id) if not org: logger.warning("Pooler cannot find org from user_count", "pooler", org_id=org_id, user_type=user_type) continue new_users.append([(org, user_type)] * (pool_size - count)) for org, user_type in utils.roundrobin(*new_users): org.new_user(type=user_type, block=False)
def publish(channels, message, extra=None, transaction=None): collection = mongo.get_collection('messages') doc = { 'message': message, } if extra: for key, val in extra.items(): doc[key] = val if transaction: collection = transaction.collection(collection.name_str) # ObjectId and timestamp must be set by server and ObjectId order must # match $natural order. Docs sent in order on client are not guaranteed # to match $natural order on server. Nonce is added to force an insert # from upsert to allow the use of $currentDate. # When using inserts manipulate=False must be set to prevent pymongo # from setting ObjectId locally. if isinstance(channels, str): doc['channel'] = channels collection.update({ 'nonce': bson.ObjectId(), }, { '$set': doc, '$currentDate': { 'timestamp': True, }, }, upsert=True) else: if transaction: for channel in channels: doc_copy = doc.copy() doc_copy['channel'] = channel tran_collection.bulk().find({ 'nonce': bson.ObjectId(), }).upsert().update({ '$set': doc_copy, '$currentDate': { 'timestamp': True, }, }) tran_collection.bulk_execute() else: bulk = collection.initialize_ordered_bulk_op() for channel in channels: doc_copy = doc.copy() doc_copy['channel'] = channel bulk.find({ 'nonce': bson.ObjectId(), }).upsert().update({ '$set': doc_copy, '$currentDate': { 'timestamp': True, }, }) bulk.execute()
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(404) user = org.get_user(id=user_id) if not user: return flask.abort(404) elif not user.sync_secret: return flask.abort(404) 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 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: return utils.response(key_conf['conf']) return utils.response('')
def sync_time(): nounce = None doc = {} try: collection = mongo.get_collection('time_sync') nounce = ObjectId() collection.insert({ 'nounce': nounce, }, manipulate=False) mongo_time_start = datetime.datetime.utcnow() doc = collection.find_one({ 'nounce': nounce, }) mongo_time = doc['_id'].generation_time.replace(tzinfo=None) settings.local.mongo_time = (mongo_time_start, mongo_time) collection.remove(doc['_id']) except: from pritunl import logger logger.exception('Failed to sync time', nounce=nounce, doc_id=doc.get('id'), ) raise
def _run_actions(self): has_bulk = mongo.has_bulk if has_bulk: collection_bulks = collections.defaultdict( lambda: collection.initialize_ordered_bulk_op()) for action_set in self.action_sets: collection_name, bulk, actions, _, _ = action_set collection = mongo.get_collection(collection_name) if has_bulk: if bulk: collection = collection_bulks[collection_name] elif actions == BULK_EXECUTE: collection = collection_bulks.pop(collection_name) collection.execute() continue else: if bulk: new_action = actions[0] for action in actions[1:]: if action[0] == 'upsert': new_action[2] = {'upsert': True} elif action[0] == 'update': new_action[1].append(action[1][0]) elif action[0] == 'remove': new_action[0] = 'remove' actions = [new_action] elif actions == BULK_EXECUTE: continue self._run_collection_actions(collection, actions)
def _logger_runner_thread(): log_queue = logger.log_queue collection = mongo.get_collection('logs') while True: try: msg_docs = [] while True: try: msg = log_queue.popleft() msg_docs.append({ 'timestamp': utils.now(), 'message': msg, }) except IndexError: break if msg_docs: yield collection.insert(msg_docs) event.Event(type=SYSTEM_LOG_UPDATED) yield interrupter_sleep(settings.app.log_db_delay) except GeneratorExit: raise except: logger.exception('Error in log runner thread', 'runners') time.sleep(0.5)
def sso_callback_get(): state = flask.request.args.get('state') user = flask.request.args.get('user') 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) org_id = settings.app.sso_org if not org_id: return flask.abort(405) org = organization.get_by_id(org_id) if not org: return flask.abort(405) usr = org.find_user(name=user) if not usr: usr = org.new_user(name=user, email=user, type=CERT_CLIENT) key_link = org.create_user_key_link(usr.id) return flask.redirect(flask.request.url_root[:-1] + key_link['view_url'])
def check_thread(): collection = mongo.get_collection('task') while True: try: cur_timestamp = utils.now() spec = { 'ttl_timestamp': {'$lt': cur_timestamp}, } for task_item in task.iter_tasks(spec): random_sleep() response = task.Task.collection.update({ '_id': task_item.id, 'ttl_timestamp': {'$lt': cur_timestamp}, }, {'$unset': { 'runner_id': '', }}) if response['updatedExisting']: run_task(task_item) except: logger.exception('Error in task check thread', 'runners') yield interrupter_sleep(settings.mongo.task_ttl)
def validate_token(admin_id, token): coll = mongo.get_collection('auth_csrf_tokens') return bool(coll.find_one({ '_id': token, 'admin_id': admin_id, }))
def sso_request_get(): state = uuid.uuid4().hex callback = flask.request.url_root + 'sso/callback' if not settings.local.sub_active: return flask.abort(405) resp = utils.request.post('https://auth.pritunl.com/request/google', json_data={ 'license': 'test', 'callback': callback, 'state': state, }, headers={ 'Content-Type': 'application/json', }) if resp.status_code != 200: if resp.status_code == 401: return flask.abort(405) return flask.abort(500) tokens_collection = mongo.get_collection('sso_tokens') tokens_collection.insert({ '_id': state, 'timestamp': utils.now(), }) data = resp.json() return flask.redirect(data['url'])
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 _logger_runner_thread(): log_queue = logger.log_queue collection = mongo.get_collection("logs") settings.local.logger_runner = True while True: try: msg_docs = [] while True: try: msg = log_queue.popleft() msg_docs.append({"timestamp": utils.now(), "message": msg}) except IndexError: break if msg_docs: yield collection.insert(msg_docs) yield interrupter_sleep(settings.app.log_db_delay) except GeneratorExit: raise except: logger.exception("Error in log runner thread", "runners") time.sleep(0.5)
def subscribe(channels, cursor_id=None, timeout=None, yield_delay=None): collection = mongo.get_collection('messages') start_time = time.time() cursor_id = cursor_id or get_cursor_id(channels) while True: try: spec = {} if isinstance(channels, str): spec['channel'] = channels else: spec['channel'] = {'$in': channels} if cursor_id: spec['_id'] = {'$gt': cursor_id} yield if PYMONGO3: cursor = collection.find(spec, cursor_type=pymongo.cursor.CursorType.TAILABLE_AWAIT).sort( '$natural', pymongo.ASCENDING) else: cursor = collection.find(spec, tailable=True, await_data=True).sort('$natural', pymongo.ASCENDING) yield while cursor.alive: for doc in cursor: cursor_id = doc['_id'] yield if doc.get('message') is not None: doc.pop('nonce', None) yield doc if yield_delay: time.sleep(yield_delay) spec = spec.copy() spec['_id'] = {'$gt': cursor_id} cursor = collection.find(spec).sort( '$natural', pymongo.ASCENDING) for doc in cursor: if doc.get('message') is not None: doc.pop('nonce', None) yield doc return if timeout and time.time() - start_time >= timeout: return yield except pymongo.errors.AutoReconnect: time.sleep(0.2)
def _host_check_thread(): collection = mongo.get_collection('hosts') while True: try: ttl_timestamp = {'$lt': utils.now() - datetime.timedelta(seconds=settings.app.host_ttl)} cursor = collection.find({ 'ping_timestamp': ttl_timestamp, }, { '_id': True, }) for doc in cursor: response = collection.update({ '_id': doc['_id'], 'ping_timestamp': ttl_timestamp, }, {'$set': { 'status': OFFLINE, 'ping_timestamp': None, }}) if response['updatedExisting']: event.Event(type=HOSTS_UPDATED) except GeneratorExit: raise except: logger.exception('Error checking host status', 'runners') yield interrupter_sleep(settings.app.host_ttl)
def sso_request_get(): state = utils.rand_str(64) secret = utils.rand_str(64) callback = flask.request.url_root + 'sso/callback' if not settings.local.sub_active: return flask.abort(405) resp = utils.request.post(AUTH_SERVER + '/request/google', json_data={ 'license': settings.app.license, 'callback': callback, 'state': state, 'secret': secret, }, headers={ 'Content-Type': 'application/json', }) if resp.status_code != 200: if resp.status_code == 401: return flask.abort(405) return flask.abort(500) tokens_collection = mongo.get_collection('sso_tokens') tokens_collection.insert({ '_id': state, 'secret': secret, 'timestamp': utils.now(), }) data = resp.json() return flask.redirect(data['url'])
def get_user_count(type=CERT_CLIENT, org_ids=None): user_collection = mongo.get_collection('users') match_spec = { 'type': type, } if org_ids: match_spec['org_id'] = {'$in': org_ids} response = user_collection.aggregate([ {'$match': match_spec}, {'$project': { '_id': True, 'org_id': True, }}, {'$group': { '_id': '$org_id', 'count': {'$sum': 1}, }}, ])['result'] org_user_count = {} for doc in response: org_user_count[doc['_id']] = doc['count'] return org_user_count
def link_servers(server_id, link_server_id, use_local_address=False): if server_id == link_server_id: raise TypeError('Server id must be different then link server id') collection = mongo.get_collection('servers') count = 0 spec = { '_id': {'$in': [server_id, link_server_id]}, } project = { '_id': True, 'status': True, 'hosts': True, 'replica_count': True } hosts = set() for doc in collection.find(spec, project): if doc['status'] == ONLINE: raise ServerLinkOnlineError('Server must be offline to link') if doc['replica_count'] > 1: raise ServerLinkReplicaError('Server has replicas') hosts_set = set(doc['hosts']) if hosts & hosts_set: raise ServerLinkCommonHostError('Servers have a common host') hosts.update(hosts_set) count += 1 if count != 2: raise ServerLinkError('Link server not found') tran = transaction.Transaction() collection = tran.collection('servers') collection.update({ '_id': server_id, 'links.server_id': {'$ne': link_server_id}, }, {'$push': { 'links': { 'server_id': link_server_id, 'user_id': None, 'use_local_address': use_local_address, }, }}) collection.update({ '_id': link_server_id, 'links.server_id': {'$ne': server_id}, }, {'$addToSet': { 'links': { 'server_id': server_id, 'user_id': None, 'use_local_address': use_local_address, }, }}) tran.commit()
def user_linked_key_page_delete(short_code): utils.rand_sleep() collection = mongo.get_collection("users_key_link") collection.remove({"short_id": short_code}) return utils.jsonify({})
def unlink_servers(server_id, link_server_id): collection = mongo.get_collection('servers') spec = { '_id': {'$in': [server_id, link_server_id]}, } project = { '_id': True, 'status': True, } for doc in collection.find(spec, project): if doc['status'] == ONLINE: raise ServerLinkOnlineError('Server must be offline to unlink') tran = transaction.Transaction() collection = tran.collection('servers') collection.update({ '_id': server_id, }, {'$pull': { 'links': {'server_id': link_server_id}, }}) collection.update({ '_id': link_server_id, }, {'$pull': { 'links': {'server_id': server_id}, }}) tran.commit()
def _check_thread(): collection = mongo.get_collection('transaction') while True: try: spec = { 'ttl_timestamp': {'$lt': utils.now()}, } for doc in collection.find(spec).sort('priority'): logger.info('Transaction timeout retrying...', 'runners', doc=doc, ) try: tran = transaction.Transaction(doc=doc) tran.run() except: logger.exception('Failed to run transaction', 'runners', transaction_id=doc['_id'], ) yield interrupter_sleep(settings.mongo.tran_ttl) except GeneratorExit: raise except: logger.exception('Error in transaction runner thread', 'runners') time.sleep(0.5)
def get_user_count(org_ids, type=CERT_CLIENT): user_collection = mongo.get_collection("users") org_user_count = {} for org_id in org_ids: org_user_count[org_id] = user_collection.find({"type": type, "org_id": org_id}, {"_id": True}).count() return org_user_count
def set_acme(token, authorization): coll = mongo.get_collection('acme_challenges') coll.insert({ '_id': token, 'authorization': authorization, 'timestamp': utils.now(), })
def user_linked_key_page_delete_get(short_id): utils.rand_sleep() collection = mongo.get_collection('users_key_link') collection.remove({ 'short_id': short_id, }) return utils.jsonify({})
def get_server_page_total(): org_collection = mongo.get_collection('servers') count = org_collection.find({}, { '_id': True, }).count() return int(math.floor(max(0, float(count - 1)) / settings.app.server_page_count))
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, 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_one({ '_id': token, }) 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.error('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.error('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.error('Duo plugin authentication not valid', 'sso', username=username, ) return flask.abort(401) groups = groups | set(groups2 or []) 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.jsonify({ 'redirect': utils.get_url_root() + key_link['view_url'], }, 200)
def sso_request_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(404) state = utils.rand_str(64) secret = utils.rand_str(64) callback = utils.get_url_root() + '/sso/callback' auth_server = AUTH_SERVER if settings.app.dedicated: auth_server = settings.app.dedicated if not settings.local.sub_active: logger.error('Subscription must be active for sso', 'sso') return flask.abort(405) if AZURE_AUTH in sso_mode: resp = requests.post(auth_server + '/v1/request/azure', headers={ 'Content-Type': 'application/json', }, json={ 'license': settings.app.license, 'callback': callback, 'state': state, 'secret': secret, 'directory_id': settings.app.sso_azure_directory_id, 'app_id': settings.app.sso_azure_app_id, 'app_secret': settings.app.sso_azure_app_secret, }, ) if resp.status_code != 200: logger.error('Azure auth server error', 'sso', status_code=resp.status_code, content=resp.content, ) if resp.status_code == 401: return flask.abort(405) return flask.abort(500) tokens_collection = mongo.get_collection('sso_tokens') tokens_collection.insert({ '_id': state, 'type': AZURE_AUTH, 'secret': secret, 'timestamp': utils.now(), }) data = resp.json() return utils.redirect(data['url']) elif GOOGLE_AUTH in sso_mode: resp = requests.post(auth_server + '/v1/request/google', headers={ 'Content-Type': 'application/json', }, json={ 'license': settings.app.license, 'callback': callback, 'state': state, 'secret': secret, }, ) if resp.status_code != 200: logger.error('Google auth server error', 'sso', status_code=resp.status_code, content=resp.content, ) if resp.status_code == 401: return flask.abort(405) return flask.abort(500) tokens_collection = mongo.get_collection('sso_tokens') tokens_collection.insert({ '_id': state, 'type': GOOGLE_AUTH, 'secret': secret, 'timestamp': utils.now(), }) data = resp.json() return utils.redirect(data['url']) elif AUTHZERO_AUTH in sso_mode: resp = requests.post(auth_server + '/v1/request/authzero', headers={ 'Content-Type': 'application/json', }, json={ 'license': settings.app.license, 'callback': callback, 'state': state, 'secret': secret, 'app_domain': settings.app.sso_authzero_domain, 'app_id': settings.app.sso_authzero_app_id, 'app_secret': settings.app.sso_authzero_app_secret, }, ) if resp.status_code != 200: logger.error('Auth0 auth server error', 'sso', status_code=resp.status_code, content=resp.content, ) if resp.status_code == 401: return flask.abort(405) return flask.abort(500) tokens_collection = mongo.get_collection('sso_tokens') tokens_collection.insert({ '_id': state, 'type': AUTHZERO_AUTH, 'secret': secret, 'timestamp': utils.now(), }) data = resp.json() return utils.redirect(data['url']) elif SLACK_AUTH in sso_mode: resp = requests.post(auth_server + '/v1/request/slack', headers={ 'Content-Type': 'application/json', }, json={ 'license': settings.app.license, 'callback': callback, 'state': state, 'secret': secret, }, ) if resp.status_code != 200: logger.error('Slack auth server error', 'sso', status_code=resp.status_code, content=resp.content, ) if resp.status_code == 401: return flask.abort(405) return flask.abort(500) tokens_collection = mongo.get_collection('sso_tokens') tokens_collection.insert({ '_id': state, 'type': SLACK_AUTH, 'secret': secret, 'timestamp': utils.now(), }) data = resp.json() return utils.redirect(data['url']) elif SAML_AUTH in sso_mode: resp = requests.post(auth_server + '/v1/request/saml', headers={ 'Content-Type': 'application/json', }, json={ 'license': settings.app.license, 'callback': callback, 'state': state, 'secret': secret, 'sso_url': settings.app.sso_saml_url, 'issuer_url': settings.app.sso_saml_issuer_url, 'cert': settings.app.sso_saml_cert, }, ) if resp.status_code != 200: logger.error('Saml auth server error', 'sso', status_code=resp.status_code, content=resp.content, ) if resp.status_code == 401: return flask.abort(405) return flask.abort(500) tokens_collection = mongo.get_collection('sso_tokens') tokens_collection.insert({ '_id': state, 'type': SAML_AUTH, 'secret': secret, 'timestamp': utils.now(), }) return flask.Response( status=200, response=resp.content, content_type="text/html;charset=utf-8", ) else: return flask.abort(404)
def collection(cls): return mongo.get_collection('servers_output')
def transaction_collection(cls): return mongo.get_collection('transaction')
def _rollback_actions(self): for action_set in self.action_sets: collection_name, _, _, rollback_actions, _ = action_set collection = mongo.get_collection(collection_name) self._run_collection_actions(collection, rollback_actions)
def sso_yubico_post(): 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_one({ '_id': token, }) if not doc or doc['_id'] != token or doc['type'] != YUBICO_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 []) valid, yubico_id = sso.auth_yubico(key) if not valid: return utils.jsonify({ 'error': YUBIKEY_INVALID, 'error_msg': YUBIKEY_INVALID_MSG, }, 401) 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, yubico_id=yubico_id, 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 yubico_id != usr.yubico_id: return utils.jsonify({ 'error': YUBIKEY_INVALID, 'error_msg': YUBIKEY_INVALID_MSG, }, 401) 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 user_net_link_collection(cls): return mongo.get_collection('users_net_link')
def host_collection(cls): return mongo.get_collection('hosts')
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 sso_push_cache_collection(cls): return mongo.get_collection('sso_push_cache')
def sso_passcode_cache_collection(cls): return mongo.get_collection('sso_passcode_cache')
def collection(cls): return mongo.get_collection('servers_ip_pool')
def sso_client_cache_collection(cls): return mongo.get_collection('sso_client_cache')
def limiter_collection(cls): return mongo.get_collection('auth_limiter')
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] groups = set(params.get('groups') or []) 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, groups2 = 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) groups = groups | set(groups2 or []) 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) groups = set(groups or []) else: username = params.get('username')[0] email = username valid, google_groups = 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) groups = set(groups or []) if settings.app.sso_google_mode == 'groups': groups = groups | set(google_groups) else: for org_name in sorted(google_groups): org = organization.get_by_name(org_name, fields=('_id')) if org: org_id = org.id break 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() 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 org_collection(cls): return mongo.get_collection('organizations')
def key_sync_get(org_id, user_id, server_id, key_hash): 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: return flask.abort(406) auth_nonce = auth_nonce[:32] try: if abs(int(auth_timestamp) - int(utils.time_now())) > \ settings.app.auth_time_window: return flask.abort(408) except ValueError: return flask.abort(405) org = organization.get_by_id(org_id) if not org: return flask.abort(404) usr = org.get_user(id=user_id) if not usr: return flask.abort(404) elif not usr.sync_secret: return flask.abort(410) if auth_token != usr.sync_token: return flask.abort(410) if usr.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: 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): 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: 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=utils.get_remote_addr(), ) 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 clients_collection(cls): return mongo.get_collection('clients')
def link_state_put(): 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 flask.abort(406) auth_nonce = auth_nonce[:32] try: if abs(int(auth_timestamp) - int(utils.time_now())) > \ settings.app.auth_time_window: return flask.abort(408) except ValueError: return flask.abort(405) host = link.get_host(utils.ObjectId(auth_token)) if not host: return flask.abort(404) auth_string = '&'.join([ auth_token, auth_timestamp, auth_nonce, flask.request.method, flask.request.path, ]) if len(auth_string) > AUTH_SIG_STRING_MAX_LEN: return flask.abort(413) auth_test_signature = base64.b64encode( hmac.new(host.secret.encode(), auth_string.encode(), hashlib.sha512).digest()).decode() if not utils.const_compare(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(), }) except pymongo.errors.DuplicateKeyError: return flask.abort(409) host.load_link() host.version = flask.request.json.get('version') host.public_address = flask.request.json.get('public_address') host.local_address = flask.request.json.get('local_address') host.address6 = flask.request.json.get('address6') state, active = host.get_state() if active: host.location.status = flask.request.json.get('status') or None host.location.commit('status') data = json.dumps(state, default=lambda x: str(x)) data += (16 - len(data) % 16) * '\x00' iv = os.urandom(16) key = hashlib.sha256(host.secret.encode()).digest() cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()).encryptor() enc_data = base64.b64encode( cipher.update(data.encode()) + cipher.finalize()) enc_signature = base64.b64encode( hmac.new(host.secret.encode(), enc_data, hashlib.sha512).digest()).decode() resp = flask.Response(response=enc_data, mimetype='application/base64') resp.headers.add('Cache-Control', 'no-cache, no-store, must-revalidate') resp.headers.add('Pragma', 'no-cache') resp.headers.add('Expires', 0) resp.headers.add('Cipher-IV', base64.b64encode(iv)) resp.headers.add('Cipher-Signature', enc_signature) return resp
def server_collection(cls): return mongo.get_collection('servers')
def dh_params_collection(cls): return mongo.get_collection('dh_params')
def users_ip_collection(cls): return mongo.get_collection('users_ip')
def _run_post_actions(self): for action_set in self.action_sets: collection_name, _, _, _, post_actions = action_set collection = mongo.get_collection(collection_name) self._run_collection_actions(collection, post_actions)
def update(): license = settings.app.license collection = mongo.get_collection('settings') if not settings.app.id: settings.app.id = utils.random_name() settings.commit() if not license: settings.local.sub_active = False settings.local.sub_status = None settings.local.sub_plan = None settings.local.sub_quantity = None settings.local.sub_amount = None settings.local.sub_period_end = None settings.local.sub_trial_end = None settings.local.sub_cancel_at_period_end = None settings.local.sub_url_key = None else: for i in xrange(2): try: response = requests.get( 'https://app.pritunl.com/subscription', json={ 'id': settings.app.id, 'license': license, 'version': settings.local.version_int, }, timeout=max(settings.app.http_request_timeout, 10), ) # License key invalid if response.status_code == 470: logger.warning('License key is invalid', 'subscription') update_license(None) update() return False if response.status_code == 473: raise ValueError(('Version %r not recognized by ' + 'subscription server') % settings.local.version_int) data = response.json() settings.local.sub_active = data['active'] settings.local.sub_status = data['status'] settings.local.sub_plan = data['plan'] settings.local.sub_quantity = data['quantity'] settings.local.sub_amount = data['amount'] settings.local.sub_period_end = data['period_end'] settings.local.sub_trial_end = data['trial_end'] settings.local.sub_cancel_at_period_end = data[ 'cancel_at_period_end'] settings.local.sub_url_key = data.get('url_key') settings.local.sub_styles[data['plan']] = data['styles'] except: if i < 1: logger.exception('Failed to check subscription status', 'subscription, retrying...') time.sleep(1) continue logger.exception('Failed to check subscription status', 'subscription') settings.local.sub_active = False settings.local.sub_status = None settings.local.sub_plan = None settings.local.sub_quantity = None settings.local.sub_amount = None settings.local.sub_period_end = None settings.local.sub_trial_end = None settings.local.sub_cancel_at_period_end = None settings.local.sub_url_key = None break if settings.app.license_plan != settings.local.sub_plan and \ settings.local.sub_plan: settings.app.license_plan = settings.local.sub_plan settings.commit() response = collection.update({ '_id': 'subscription', '$or': [ {'active': {'$ne': settings.local.sub_active}}, {'plan': {'$ne': settings.local.sub_plan}}, ], }, {'$set': { 'active': settings.local.sub_active, 'plan': settings.local.sub_plan, }}) if response['updatedExisting']: if settings.local.sub_active: if settings.local.sub_plan == 'premium': event.Event(type=SUBSCRIPTION_PREMIUM_ACTIVE) elif settings.local.sub_plan == 'enterprise': event.Event(type=SUBSCRIPTION_ENTERPRISE_ACTIVE) else: event.Event(type=SUBSCRIPTION_NONE_INACTIVE) else: if settings.local.sub_plan == 'premium': event.Event(type=SUBSCRIPTION_PREMIUM_INACTIVE) elif settings.local.sub_plan == 'enterprise': event.Event(type=SUBSCRIPTION_ENTERPRISE_INACTIVE) else: event.Event(type=SUBSCRIPTION_NONE_INACTIVE) return True
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] 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] 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] 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] 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] 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 otp_cache_collection(cls): return mongo.get_collection('otp_cache')
def sso_duo_post(): remote_addr = utils.get_remote_addr() 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: journal.entry( journal.SSO_AUTH_FAILURE, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_INVALID_TOKEN, reason_long='Invalid Duo 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 []) if settings.app.sso_duo_mode == 'passcode': duo_auth = sso.Duo( username=username, factor=settings.app.sso_duo_mode, remote_ip=remote_addr, auth_type='Key', passcode=passcode, ) valid = duo_auth.authenticate() if not valid: logger.warning('Duo authentication not valid', 'sso', username=username, ) journal.entry( journal.SSO_AUTH_FAILURE, username=username, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_DUO_FAILED, reason_long='Duo passcode authentication failed', ) 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=remote_addr, auth_type='Key', ) valid = duo_auth.authenticate() if not valid: logger.warning('Duo authentication not valid', 'sso', username=username, ) journal.entry( journal.SSO_AUTH_FAILURE, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_DUO_FAILED, reason_long='Duo authentication failed', ) 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=remote_addr, ) if valid: org_id = org_id_new or org_id else: logger.warning('Duo 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='Duo plugin authentication failed', ) return flask.abort(401) groups = groups | set(groups2 or []) return _validate_user(username, email, sso_mode, org_id, groups, remote_addr)
def collection(cls): return mongo.get_collection('users')
def user_get(org_id, user_id=None, page=None): org = organization.get_by_id(org_id) if user_id: return utils.jsonify(org.get_user(user_id).dict()) page = flask.request.args.get('page', None) page = int(page) if page else page search = flask.request.args.get('search', None) limit = int(flask.request.args.get('limit', settings.user.page_count)) otp_auth = False dns_mapping = False server_count = 0 servers = [] for svr in org.iter_servers(fields=('name', 'otp_auth', 'dns_mapping')): servers.append(svr) server_count += 1 if svr.otp_auth: otp_auth = True if svr.dns_mapping: dns_mapping = True users = [] users_id = [] users_data = {} users_servers = {} fields = ( 'organization', 'organization_name', 'name', 'email', 'pin', 'type', 'auth_type', 'otp_secret', 'disabled', 'bypass_secondary', 'dns_servers', 'dns_suffix', ) for usr in org.iter_users(page=page, search=search, search_limit=limit, fields=fields): users_id.append(usr.id) user_dict = usr.dict() user_dict['gravatar'] = settings.user.gravatar user_dict['audit'] = settings.app.auditing == ALL user_dict['status'] = False user_dict['sso'] = settings.app.sso user_dict['otp_auth'] = otp_auth if dns_mapping: user_dict['dns_mapping'] = ( '%s.%s.vpn' % (usr.name.split('@')[0], org.name)).lower() else: user_dict['dns_mapping'] = None user_dict['network_links'] = [] users_data[usr.id] = user_dict users_servers[usr.id] = {} server_data = [] for svr in servers: data = { 'id': svr.id, 'name': svr.name, 'status': False, 'server_id': svr.id, 'device_name': None, 'platform': None, 'real_address': None, 'virt_address': None, 'virt_address6': None, 'connected_since': None } server_data.append(data) users_servers[usr.id][svr.id] = data user_dict['servers'] = sorted(server_data, key=lambda x: x['name']) users.append(user_dict) clients_collection = mongo.get_collection('clients') for doc in clients_collection.find({ 'user_id': { '$in': users_id }, }): server_data = users_servers[doc['user_id']].get(doc['server_id']) if not server_data: continue users_data[doc['user_id']]['status'] = True if server_data['status']: server_data = { 'name': server_data['name'], } append = True else: append = False virt_address6 = doc.get('virt_address6') if virt_address6: server_data['virt_address6'] = virt_address6.split('/')[0] server_data['id'] = doc['_id'] server_data['status'] = True server_data['server_id'] = server_data['id'] server_data['device_name'] = doc['device_name'] server_data['platform'] = doc['platform'] server_data['real_address'] = doc['real_address'] server_data['virt_address'] = doc['virt_address'].split('/')[0] server_data['connected_since'] = doc['connected_since'] if append: svrs = users_data[doc['user_id']]['servers'] svrs.append(server_data) users_data[doc['user_id']]['servers'] = sorted( svrs, key=lambda x: x['name']) net_link_collection = mongo.get_collection('users_net_link') for doc in net_link_collection.find({ 'user_id': { '$in': users_id }, }): users_data[doc['user_id']]['network_links'].append(doc['network']) ip_addrs_iter = server.multi_get_ip_addr(org_id, users_id) for user_id, server_id, addr, addr6 in ip_addrs_iter: server_data = users_servers[user_id].get(server_id) if server_data: if not server_data['virt_address']: server_data['virt_address'] = addr if not server_data['virt_address6']: server_data['virt_address6'] = addr6 if page is not None: return utils.jsonify({ 'page': page, 'page_total': org.page_total, 'server_count': server_count, 'users': users, }) elif search is not None: return utils.jsonify({ 'search': search, 'search_more': limit < org.last_search_count, 'search_limit': limit, 'search_count': org.last_search_count, 'search_time': round((time.time() - flask.g.start), 4), 'server_count': server_count, 'users': users, }) else: return utils.jsonify(users)
def routes_collection(cls): return mongo.get_collection('routes_reserve')