async def create_new_role(request): """Create a new role.""" required_fields = ["name", "administrators", "owners"] utils.validate_fields(required_fields, request.json) conn = await db_utils.create_connection( request.app.config.DB_HOST, request.app.config.DB_PORT, request.app.config.DB_NAME, ) response = await roles_query.roles_search_duplicate(conn, request.json.get("name")) if not response: txn_key, txn_user_id = await utils.get_transactor_key(request) role_id = str(uuid4()) batch_list = Role().batch_list( signer_keypair=txn_key, signer_user_id=txn_user_id, name=request.json.get("name"), role_id=role_id, metadata=request.json.get("metadata"), admins=request.json.get("administrators"), owners=request.json.get("owners"), description=request.json.get("description"), ) await utils.send( request.app.config.VAL_CONN, batch_list, request.app.config.TIMEOUT ) return create_role_response(request, role_id) raise ApiBadRequest( "Error: could not create this role because role name has been taken or already exists" )
async def add_role_task(request, role_id): """Add a task to a role.""" required_fields = ["id"] utils.validate_fields(required_fields, request.json) txn_key, txn_user_id = await utils.get_transactor_key(request) proposal_id = str(uuid4()) conn = await create_connection() approver = await fetch_relationships( "task_owners", "task_id", request.json.get("id") ).run(conn) conn.close() batch_list = Role().task.propose.batch_list( signer_keypair=txn_key, signer_user_id=txn_user_id, proposal_id=proposal_id, role_id=role_id, task_id=request.json.get("id"), reason=request.json.get("reason"), metadata=request.json.get("metadata"), assigned_approver=approver, ) await utils.send( request.app.config.VAL_CONN, batch_list, request.app.config.TIMEOUT ) return json({"proposal_id": proposal_id})
async def add_role_member(request, role_id): """Add a member to a role.""" required_fields = ["id"] utils.validate_fields(required_fields, request.json) txn_key, txn_user_id = await utils.get_transactor_key(request) proposal_id = str(uuid4()) batch_list = Role().member.propose.batch_list( signer_keypair=txn_key, signer_user_id=txn_user_id, proposal_id=proposal_id, role_id=role_id, pack_id=request.json.get("pack_id"), next_id=request.json.get("id"), reason=request.json.get("reason"), metadata=request.json.get("metadata"), ) batch_status = await utils.send( request.app.config.VAL_CONN, batch_list, request.app.config.TIMEOUT, request.json.get("tracker") and True, ) if request.json.get("tracker"): return utils.create_tracker_response("batch_status", batch_status) return json({"proposal_id": proposal_id})
async def update_password(request): """Update a user's password. The request must come from an admin. Args: request: obj: a request object """ log_request(request) env = Env() if not env.int("ENABLE_NEXT_BASE_USE"): raise ApiDisabled("Not a valid action. Source not enabled") required_fields = ["next_id", "password"] validate_fields(required_fields, request.json) txn_key, txn_user_id = await get_transactor_key(request) is_admin = await check_admin_status(txn_user_id) if not is_admin: raise ApiBadRequest("You are not a NEXT Administrator.") salt = hashlib.sha256(os.urandom(60)).hexdigest().encode("utf-8") password = request.json.get("password").encode("utf-8") hashed_password = hashlib.pbkdf2_hmac("sha256", password, salt, 100000).hex() conn = await create_connection() await users_query.update_user_password(conn, request.json.get("next_id"), hashed_password=hashed_password, salt=salt) conn.close() return json({"message": "Password successfully updated"})
async def add_role_owner(request, role_id): """Add an owner to a role.""" log_request(request) env = Env() if not env.int("ENABLE_NEXT_BASE_USE"): raise ApiDisabled("Not a valid action. Source not enabled") required_fields = ["id"] validate_fields(required_fields, request.json) txn_key, txn_user_id = await get_transactor_key(request) proposal_id = str(uuid4()) conn = await create_connection() approver = await fetch_relationships("role_admins", "role_id", role_id).run(conn) conn.close() batch_list = Role().owner.propose.batch_list( signer_keypair=txn_key, signer_user_id=txn_user_id, proposal_id=proposal_id, role_id=role_id, next_id=request.json.get("id"), reason=request.json.get("reason"), metadata=request.json.get("metadata"), assigned_approver=approver, ) await send(request.app.config.VAL_CONN, batch_list, request.app.config.TIMEOUT) if isinstance(approver, list): for user in approver: await send_notification(user, proposal_id) else: await send_notification(approver, proposal_id) return json({"proposal_id": proposal_id})
async def add_task_owner(request, task_id): """Propose add a task owner.""" log_request(request) required_fields = ["id"] validate_fields(required_fields, request.json) txn_key, txn_user_id = await get_transactor_key(request) proposal_id = str(uuid4()) conn = await create_connection() approver = await fetch_relationships("task_admins", "task_id", task_id).run(conn) conn.close() batch_list = Task().owner.propose.batch_list( signer_keypair=txn_key, signer_user_id=txn_user_id, proposal_id=proposal_id, task_id=task_id, next_id=request.json.get("id"), reason=request.json.get("reason"), metadata=request.json.get("metadata"), assigned_approver=approver, ) await send(request.app.config.VAL_CONN, batch_list, request.app.config.TIMEOUT) return json({"proposal_id": proposal_id})
async def chatbot_webhook(request): """Webhook enabling the chatbot engine to execute RBAC actions where tracker is the full JSON representation of the state of a given conversation""" required_fields = ["tracker", "sender_id"] utils.validate_fields(required_fields, request.json) return await execute_action_add_role_member(request)
async def update_manager(request, next_id): """Update a user's manager.""" log_request(request) env = Env() if not env.int("ENABLE_NEXT_BASE_USE"): raise ApiDisabled("Not a valid action. Source not enabled") required_fields = ["id"] validate_fields(required_fields, request.json) txn_key, txn_user_id = await get_transactor_key(request) proposal_id = str(uuid4()) if await check_admin_status(txn_user_id): conn = await create_connection() next_admins_list = await users_query.get_next_admins(conn) conn.close() batch_list = User().manager.propose.batch_list( signer_keypair=txn_key, signer_user_id=txn_user_id, proposal_id=proposal_id, next_id=next_id, new_manager_id=request.json.get("id"), reason=request.json.get("reason"), metadata=request.json.get("metadata"), assigned_approver=next_admins_list, ) await send(request.app.config.VAL_CONN, batch_list, request.app.config.TIMEOUT) await send_notification(request.json.get("id"), proposal_id) else: raise ApiBadRequest("Proposal opener is not a Next Admin.") return json({"proposal_id": proposal_id})
async def batch_update_proposals(request): """Update multiple proposals""" required_fields = ["ids"] utils.validate_fields(required_fields, request.json) for proposal_id in request.json["ids"]: await update_proposal(request, proposal_id) return json({"proposal_ids": request.json["ids"]})
async def update_password(request): """Update a user's password. The request must come from an admin. Args: request: obj: a request object """ env = Env() next_enabled = env.int("ENABLE_NEXT_BASE_USE", 0) if not next_enabled: raise ApiBadRequest("This capability is not enabled for this mode.") required_fields = ["next_id", "password"] utils.validate_fields(required_fields, request.json) txn_key, txn_user_id = await utils.get_transactor_key(request) is_admin = await utils.check_admin_status(txn_user_id) if not is_admin: raise ApiBadRequest("You are not a NEXT Administrator.") hashed_pwd = hashlib.sha256( request.json.get("password").encode("utf-8") ).hexdigest() conn = await create_connection() await users_query.update_user_password( conn, request.json.get("next_id"), hashed_pwd ) conn.close() return json({"message": "Password successfully updated"})
async def update_proposal(request, proposal_id): LOGGER.debug("update proposal %s\n%s", proposal_id, request.json) required_fields = ["reason", "status"] utils.validate_fields(required_fields, request.json) if request.json["status"] not in [Status.REJECTED, Status.APPROVED]: raise ApiBadRequest( "Bad Request: status must be either 'REJECTED' or 'APPROVED'") txn_key, txn_user_id = await utils.get_transactor_key(request=request) block = await utils.get_request_block(request) conn = await db_utils.create_connection( request.app.config.DB_HOST, request.app.config.DB_PORT, request.app.config.DB_NAME, ) proposal_resource = await proposals_query.fetch_proposal_resource( conn, proposal_id=proposal_id, head_block_num=block.get("num")) conn.close() batch_list = PROPOSAL_TRANSACTION[proposal_resource.get("type")][ request.json["status"]]( signer_keypair=txn_key, signer_user_id=txn_user_id, proposal_id=proposal_id, object_id=proposal_resource.get("object"), related_id=proposal_resource.get("target"), reason=request.json.get("reason"), ) await utils.send(request.app.config.VAL_CONN, batch_list, request.app.config.TIMEOUT) return json({"proposal_id": proposal_id})
async def authorize(request): """ User login (authorization) """ required_fields = ["id", "password"] utils.validate_fields(required_fields, request.json) password = request.json.get("password") hashed_pwd = hashlib.sha256(password.encode("utf-8")).hexdigest() auth_info = await auth_query.fetch_info_by_username(request) email = auth_info.get("email") if auth_info.get("hashed_password") is None: if request.app.config.DEMO_MODE: token = generate_api_key( request.app.config.SECRET_KEY, auth_info.get("user_id") ) return utils.create_authorization_response( token, { "message": "Authorization (demo mode) successful", "user_id": auth_info.get("user_id"), }, ) if not email: raise ApiUnauthorized( "Unauthorized: No password nor email is set on this account" ) # TODO: send email confirmation with password set link raise ApiUnauthorized("Unauthorized: No password is set") if auth_info.get("hashed_password") != hashed_pwd: # TODO: rate limit password attempts raise ApiUnauthorized("Unauthorized: Incorrect password") token = generate_api_key(request.app.config.SECRET_KEY, auth_info.get("user_id")) return utils.create_authorization_response( token, {"message": "Authorization successful", "user_id": auth_info.get("user_id")}, )
async def create_corpuser(request): """Create a new CORP user.""" required_fields = ["id", "password"] utils.validate_fields(required_fields, request.json) log_request(request, True) env = Env() username = env("ADAPI_USERNAME") password = env("ADAPI_PASSWORD") auth = aiohttp.BasicAuth(login=username, password=password) url = ADAPI_REST_ENDPOINT + "?command=new-corpuser" data = { "ntid": request.json.get("id"), "userName": request.json.get("id"), "password": request.json.get("password"), } conn = aiohttp.TCPConnector( limit=request.app.config.AIOHTTP_CONN_LIMIT, ttl_dns_cache=request.app.config.AIOHTTP_DNS_TTL, verify_ssl=False, ) async with aiohttp.ClientSession(connector=conn, auth=auth) as session: async with session.post(url=url, json=data) as response: data = await response.read() res = json.loads(data.decode("utf-8")) if res.get("success") == "false": raise ApiBadRequest("Invalid CORP account request.") return sanic_json({"data": {"message": "CORP account request successful."}})
async def update_proposal(request, proposal_id): required_fields = ["reason", "status"] utils.validate_fields(required_fields, request.json) if request.json["status"] not in [Status.REJECTED, Status.APPROVED]: raise ApiBadRequest( "Bad Request: status must be either 'REJECTED' or 'APPROVED'") txn_key = await utils.get_transactor_key(request=request) block = await utils.get_request_block(request) proposal_resource = await proposals_query.fetch_proposal_resource( request.app.config.DB_CONN, proposal_id=proposal_id, head_block_num=block.get("num"), ) batch_list, _ = PROPOSAL_TRANSACTION[proposal_resource.get("type")][ request.json["status"]]( txn_key, request.app.config.BATCHER_KEY_PAIR, proposal_id, proposal_resource.get("object"), proposal_resource.get("target"), request.json.get("reason"), ) await utils.send(request.app.config.VAL_CONN, batch_list, request.app.config.TIMEOUT) return json({"proposal_id": proposal_id})
async def update_manager(request, user_id): required_fields = ["id"] utils.validate_fields(required_fields, request.json) txn_key, txn_user_id = await utils.get_transactor_key(request) proposal_id = str(uuid4()) batch_list = rbac.user.manager.propose.batch_list( signer_keypair=txn_key, signer_user_id=txn_user_id, proposal_id=proposal_id, user_id=user_id, new_manager_id=request.json.get("id"), reason=request.json.get("reason"), metadata=request.json.get("metadata"), ) conn = await db_utils.create_connection( request.app.config.DB_HOST, request.app.config.DB_PORT, request.app.config.DB_NAME, ) await utils.send(conn, batch_list, request.app.config.TIMEOUT) conn.close() return json({"proposal_id": proposal_id})
async def add_pack_role(request, pack_id): """Add roles to a pack""" required_fields = ["roles"] utils.validate_fields(required_fields, request.json) await packs_query.add_roles(request.app.config.DB_CONN, pack_id, request.json.get("roles")) return json({"roles": request.json.get("roles")})
async def create_new_pack(request): """Create a new pack""" required_fields = ["owners", "name", "roles"] utils.validate_fields(required_fields, request.json) conn = await db_utils.create_connection( request.app.config.DB_HOST, request.app.config.DB_PORT, request.app.config.DB_NAME, ) response = await packs_query.packs_search_duplicate( conn, request.json.get("name")) if not response: pack_id = str(uuid4()) await packs_query.create_pack_resource( conn, pack_id, request.json.get("owners"), request.json.get("name"), request.json.get("description"), ) await packs_query.add_roles(conn, pack_id, request.json.get("roles")) conn.close() return create_pack_response(request, pack_id) raise ApiBadRequest( "Error: could not create this pack because pack name has been taken or already exists" )
async def update_proposal(request, proposal_id): """Update proposal.""" log_request(request) LOGGER.debug("update proposal %s\n%s", proposal_id, request.json) required_fields = ["reason", "status"] validate_fields(required_fields, request.json) if request.json["status"] not in ("REJECTED", "APPROVED"): raise ApiBadRequest( "Bad Request: status must be either 'REJECTED' or 'APPROVED'" ) txn_key, txn_user_id = await get_transactor_key(request=request) conn = await create_connection() proposal_resource = await proposals_query.fetch_proposal_resource( conn, proposal_id=proposal_id ) approvers_list = await compile_proposal_resource(conn, proposal_resource) conn.close() if txn_user_id not in approvers_list["approvers"]: raise ApiUnauthorized( "Bad Request: You don't have the authorization to APPROVE or REJECT the proposal" ) batch_list = PROPOSAL_TRANSACTION[proposal_resource.get("type")][ request.json["status"] ].batch_list( signer_keypair=txn_key, signer_user_id=txn_user_id, proposal_id=proposal_id, object_id=proposal_resource.get("object"), related_id=proposal_resource.get("target"), reason=request.json.get("reason"), ) await send(request.app.config.VAL_CONN, batch_list, request.app.config.TIMEOUT) await send_notification(proposal_resource.get("target"), proposal_id) return json({"proposal_id": proposal_id})
async def create_new_role(request): """Create a new role.""" required_fields = ["name", "administrators", "owners"] utils.validate_fields(required_fields, request.json) conn = await create_connection() role_title = " ".join(request.json.get("name").split()) response = await roles_query.roles_search_duplicate(conn, role_title) if request.json.get("metadata") is None or request.json.get("metadata") == {}: set_metadata = {} else: set_metadata = request.json.get("metadata") set_metadata["sync_direction"] = "OUTBOUND" if not response: txn_key, txn_user_id = await utils.get_transactor_key(request) role_id = str(uuid4()) batch_list = Role().batch_list( signer_keypair=txn_key, signer_user_id=txn_user_id, name=role_title, role_id=role_id, metadata=set_metadata, admins=request.json.get("administrators"), owners=request.json.get("owners"), description=request.json.get("description"), ) await utils.send( request.app.config.VAL_CONN, batch_list, request.app.config.TIMEOUT ) conn.close() if role_title != "NextAdmins": distinguished_name_formatted = "CN=" + role_title + "," + GROUP_BASE_DN data_formatted = { "created_date": r.now(), "distinguished_name": distinguished_name_formatted, "group_nickname": role_title, "group_types": -2147483646, "name": role_title, "remote_id": distinguished_name_formatted, } outbound_entry = { "data": data_formatted, "data_type": "group", "timestamp": r.now(), "provider_id": LDAP_DC, } # Insert to outbound_queue and close conn = await create_connection() role_outbound_resource = await roles_query.insert_to_outboundqueue( conn, outbound_entry ) conn.close() else: LOGGER.info( "The role being created is NextAdmins, which is local to NEXT and will not be inserted into the outbound_queue." ) return create_role_response(request, role_id) raise ApiBadRequest( "Error: Could not create this role because the role name already exists." )
async def chatbot(request, ws): while True: required_fields = ["do", "message", "user_id"] recv = json.loads(await ws.recv()) utils.validate_fields(required_fields, recv) response = await create_response(request, recv) await ws.send(response)
async def feed(request, ws): """Socket feed enabling real-time notifications""" while True: required_fields = ["user_id"] recv = json.loads(await ws.recv()) utils.validate_fields(required_fields, recv) await proposal_feed(request, ws, recv)
async def update_expired_roles(request, next_id): """Manually expire user role membership""" required_fields = ["id"] utils.validate_fields(required_fields, request.json) await roles_query.expire_role_member(request.app.config.DB_CONN, request.json.get("id"), next_id) return json({"role_id": request.json.get("id")})
async def create_new_pack(request): """Create a new pack""" required_fields = ["name"] utils.validate_fields(required_fields, request.json) pack_id = str(uuid4()) await packs_query.create_pack_resource(request.app.config.DB_CONN, pack_id, request.json.get("name")) return create_pack_response(request, pack_id)
async def chatbot(request, web_socket): """Chatbot websocket listener.""" while True: required_fields = ["text", "next_id"] recv = json.loads(await web_socket.recv()) utils.validate_fields(required_fields, recv) response = await create_response(request, recv) await web_socket.send(response)
async def update_expired_roles(request, next_id): """Manually expire user role membership""" required_fields = ["id"] utils.validate_fields(required_fields, request.json) conn = await create_connection() await roles_query.expire_role_member(conn, request.json.get("id"), next_id) conn.close() return json({"role_id": request.json.get("id")})
async def add_pack_role(request, pack_id): """Add roles to a pack""" log_request(request) required_fields = ["roles"] validate_fields(required_fields, request.json) conn = await create_connection() await packs_query.add_roles(conn, pack_id, request.json.get("roles")) conn.close() return json({"roles": request.json.get("roles")})
async def feed(request, web_socket): """Socket feed enabling real-time notifications""" LOGGER.info(request) while True: required_fields = ["next_id"] recv = json.loads(await web_socket.recv()) utils.validate_fields(required_fields, recv) await proposal_feed(web_socket, recv)
async def add_pack_member(request, pack_id): """Add a member to the roles of a pack""" required_fields = ["id"] utils.validate_fields(required_fields, request.json) head_block = await utils.get_request_block(request) pack_resource = await packs_query.fetch_pack_resource( request.app.config.DB_CONN, pack_id, head_block.get("num")) for role_id in pack_resource.get("roles"): await roles.add_role_member(request, role_id) return json({"pack_id": pack_id})
async def execute_action_add_role_member(request): """"Webhook action to create a new proposal given slot data collected by the chatbot engine through dialog with a user""" required_fields = ["reason", "resource_id"] utils.validate_fields(required_fields, request.json["tracker"].get("slots")) request.json["id"] = request.json.get("sender_id") request.json["reason"] = request.json["tracker"]["slots"].get("reason") return await roles.add_role_member( request, request.json["tracker"]["slots"].get("resource_id"))
async def add_pack_member(request, pack_id): """Add a member to the roles of a pack""" required_fields = ["id"] utils.validate_fields(required_fields, request.json) pack_resource = await packs_query.fetch_pack_resource( request.app.config.DB_CONN, pack_id) request.json["metadata"] = "" request.json["pack_id"] = pack_id for role_id in pack_resource.get("roles"): await roles.add_role_member(request, role_id) return json({"pack_id": pack_id})