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 delete_user(request, next_id): """Delete a specific user by next_id.""" log_request(request) env = Env() if not env.int("ENABLE_NEXT_BASE_USE"): raise ApiDisabled("Not a valid action. Source not enabled.") txn_list = [] txn_key, _ = await get_transactor_key(request) txn_list = await create_del_ownr_by_user_txns(txn_key, next_id, txn_list) txn_list = await create_del_admin_by_user_txns(txn_key, next_id, txn_list) txn_list = await create_del_mmbr_by_user_txns(txn_key, next_id, txn_list) txn_list = create_delete_user_txns(txn_key, next_id, txn_list) if txn_list: batch = batcher.make_batch_from_txns(transactions=txn_list, signer_keypair=txn_key) batch_list = batcher.batch_to_list(batch=batch) await send(request.app.config.VAL_CONN, batch_list, request.app.config.TIMEOUT) await reject_users_proposals(next_id, request) return json({ "message": "User {} successfully deleted".format(next_id), "deleted": 1 })
async def check_role_name(request): """Check if a role exists with provided name.""" log_request(request) conn = await create_connection() response = await roles_query.roles_search_duplicate(conn, request.args.get("name")) conn.close() return json({"exists": bool(response)})
async def delete_pack(request, pack_id): """Delete pack from NEXT Args: request: object: request object pack_id: str: ID of pack to delete """ log_request(request) _, txn_user_id = await get_transactor_key(request) conn = await create_connection() pack = await packs_query.get_pack_by_pack_id(conn, pack_id) if not pack: raise ApiBadRequest( "Error: Pack does not currently exist or has already been deleted." ) owners = await packs_query.get_pack_owners_by_id(conn, pack_id) if txn_user_id not in owners and not await check_admin_status(txn_user_id): raise ApiForbidden( "Error: You do not have the authorization to delete this pack.") await packs_query.delete_pack_by_id(conn, pack_id) conn.close() return json({ "message": "Pack {} successfully deleted".format(pack_id), "deleted": 1, "id": pack_id, })
async def fetch_open_proposals(request, next_id): """Get open proposals for a user, by their next_id. Args: request: obj: request object to api next_id: str: next_id of user for open proposals as assigned_approval """ log_request(request) head_block = await get_request_block(request) start, limit = get_request_paging_info(request) conn = await create_connection() proposals = await proposals_query.fetch_all_proposal_resources( conn, start, limit) proposal_resources = [] for proposal in proposals: proposal_resource = await compile_proposal_resource(conn, proposal) proposal_resources.append(proposal_resource) conn.close() open_proposals = [] for proposal_resource in proposal_resources: if (proposal_resource["status"] == "OPEN" and next_id in proposal_resource["assigned_approver"]): open_proposals.append(proposal_resource) return await create_response(conn, request.url, open_proposals, head_block, start=start, limit=limit)
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 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 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 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 fetch_rejected_proposals(request, next_id): """Get confirmed proposals for a user, by their next_id.""" log_request(request) head_block = await get_request_block(request) start, limit = get_request_paging_info(request) conn = await create_connection() proposals = await proposals_query.fetch_all_proposal_resources( conn, start, limit) proposal_resources = [] for proposal in proposals: proposal_resource = await compile_proposal_resource(conn, proposal) proposal_resources.append(proposal_resource) conn.close() rejected_proposals = [] for proposal_resource in proposal_resources: if (proposal_resource["status"] == "REJECTED" and next_id in proposal_resource["approvers"]): rejected_proposals.append(proposal_resource) return await create_response(conn, request.url, rejected_proposals, head_block, start=start, limit=limit)
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 batch_update_proposals(request): """Update multiple proposals""" log_request(request) required_fields = ["ids"] 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 get_role(request, role_id): """Get a specific role by role_id.""" log_request(request) head_block = await get_request_block(request) conn = await create_connection() role_resource = await roles_query.fetch_role_resource(conn, role_id) conn.close() return await create_response(conn, request.url, role_resource, head_block)
async def get_task(request, task_id): """Get a specific task by task_id.""" log_request(request) head_block = await get_request_block(request) conn = await create_connection() task_resource = await tasks_query.fetch_task_resource(conn, task_id) conn.close() return await create_response(conn, request.url, task_resource, head_block)
async def get_proposal(request, proposal_id): """Get specific proposal by proposal_id.""" log_request(request) head_block = await get_request_block(request) conn = await create_connection() proposal = await proposals_query.fetch_proposal_resource(conn, proposal_id) proposal_resource = await compile_proposal_resource(conn, proposal) conn.close() return await create_response(conn, request.url, proposal_resource, head_block)
async def get_pack(request, pack_id): """Get a single pack""" log_request(request) head_block = await get_request_block(request) conn = await create_connection() pack_resource = await packs_query.fetch_pack_resource(conn, pack_id) conn.close() return await create_response(conn, request.url, pack_resource, head_block)
async def get_user_relationships(request, next_id): """Get relationships for a specific user, by next_id.""" log_request(request) head_block = await get_request_block(request) conn = await create_connection() user_resource = await users_query.fetch_user_relationships(conn, next_id) conn.close() return await create_response(conn, request.url, user_resource, head_block)
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 get_user_summary(request, next_id): """This endpoint is for returning summary data for a user, just it's next_id,name, email.""" log_request(request) head_block = await get_request_block(request) conn = await create_connection() user_resource = await users_query.fetch_user_resource_summary( conn, next_id) conn.close() return await create_response(conn, request.url, user_resource, head_block)
async def update_expired_roles(request, next_id): """Manually expire user role membership""" log_request(request) required_fields = ["id"] 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 get_all_roles(request): """Get all roles.""" log_request(request) head_block = await get_request_block(request) start, limit = get_request_paging_info(request) conn = await create_connection() role_resources = await roles_query.fetch_all_role_resources(conn, start, limit) conn.close() return await create_response( conn, request.url, role_resources, head_block, start=start, limit=limit )
async def get_latest_block(request): """Get the newest block on blockchain.""" log_request(request) if "?head=" in request.url: raise ApiBadRequest( "Bad Request: 'head' parameter should not be specified") conn = await create_connection() block_resource = await blocks_query.fetch_latest_block_with_retry(conn) conn.close() url = request.url.replace("latest", block_resource.get("id")) return json({"data": block_resource, "link": url})
async def create_new_role(request): """Create a new 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 = ["name", "administrators", "owners"] validate_fields(required_fields, request.json) role_title = " ".join(request.json.get("name").split()) conn = await create_connection() response = await roles_query.roles_search_duplicate(conn, role_title) conn.close() if not response: txn_key, txn_user_id = await get_transactor_key(request) role_id = str(uuid4()) if request.json.get("metadata") is None: set_metadata = {} else: set_metadata = request.json.get("metadata") set_metadata["sync_direction"] = "OUTBOUND" 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"), ) sawtooth_response = await send( request.app.config.VAL_CONN, batch_list, request.app.config.TIMEOUT ) if not sawtooth_response: LOGGER.warning("There was an error submitting the sawtooth transaction.") return await handle_errors( request, ApiInternalError( "There was an error submitting the sawtooth transaction." ), ) return create_role_response(request, role_id) return await handle_errors( request, ApiTargetConflict( "Error: Could not create this role because the role name already exists." ), )
async def add_pack_member(request, pack_id): """Add a member to the roles of a pack""" log_request(request) required_fields = ["id"] validate_fields(required_fields, request.json) conn = await create_connection() pack_resource = await packs_query.fetch_pack_resource(conn, pack_id) conn.close() request.json["metadata"] = "" request.json["pack_id"] = pack_id for role_id in pack_resource.get("roles"): await add_role_member(request, role_id) return json({"pack_id": pack_id})
async def get_all_proposals(request): """Get all proposals""" log_request(request) head_block = await get_request_block(request) start, limit = get_request_paging_info(request) conn = await create_connection() proposals = await proposals_query.fetch_all_proposal_resources(conn, start, limit) proposal_resources = [] for proposal in proposals: proposal_resource = await compile_proposal_resource(conn, proposal) proposal_resources.append(proposal_resource) conn.close() return await create_response( conn, request.url, proposal_resources, head_block, start=start, limit=limit )
async def search_all(request): """API Endpoint to get all roles, packs, or users containing a string.""" log_request(request) search_query = request.json.get("query") # Check for valid payload containing query and search object types errors = validate_search_payload(search_query) if errors: return json(errors) # Create response data object data = {"packs": [], "roles": [], "users": []} # Pagination and total pages try: paging = search_paginate(search_query["page_size"], search_query["page"]) except KeyError: paging = (0, 50) object_counts = [] # Run search queries conn = await create_connection() if "pack" in search_query["search_object_types"]: # Fetch packs with search input string pack_results = await search_packs(conn, search_query, paging) data["packs"] = pack_results object_counts.append(await search_packs_count(conn, search_query)) if "role" in search_query["search_object_types"]: # Fetch roles with search input string role_results = await search_roles(conn, search_query, paging) data["roles"] = role_results object_counts.append(await search_roles_count(conn, search_query)) if "user" in search_query["search_object_types"]: # Fetch users with search input string user_results = await search_users(conn, search_query, paging) data["users"] = user_results object_counts.append(await search_users_count(conn, search_query)) conn.close() total_pages = get_total_pages(object_counts, search_query["page_size"]) return json( {"data": data, "page": search_query["page"], "total_pages": total_pages} )
async def get_all_blocks(request): """Get all blocks.""" conn = await create_connection() log_request(request) head_block = await get_request_block(request) start, limit = get_request_paging_info(request) block_resources = await blocks_query.fetch_all_blocks( conn, head_block.get("num"), start, limit) conn.close() return await create_response(conn, request.url, block_resources, head_block, start=start, limit=limit)
async def fetch_all_users(request): """Returns all users.""" log_request(request) head_block = await get_request_block(request) start, limit = get_request_paging_info(request) conn = await create_connection() user_resources = await users_query.fetch_all_user_resources( conn, start, limit) conn.close() return await create_response(conn, request.url, user_resources, head_block, start=start, limit=limit)
async def update_role(request, role_id): """Update 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 = ["description"] validate_fields(required_fields, request.json) txn_key, txn_user_id = await get_transactor_key(request) role_description = request.json.get("description") batch_list = Role().update.batch_list( signer_keypair=txn_key, signer_user_id=txn_user_id, role_id=role_id, description=role_description, ) await send(request.app.config.VAL_CONN, batch_list, request.app.config.TIMEOUT) return json({"id": role_id, "description": role_description})
async def authorize(request): """ API Endpoint to authenticate and login to the NEXT platform. """ required_fields = ["id", "password"] utils.validate_fields(required_fields, request.json) log_request(request, True) username = request.json.get("id") password = request.json.get("password") env = Env() if username == "" or password == "": raise ApiBadRequest(LDAP_ERR_MESSAGES["default"]) user = await get_user_by_username(request) if not user: raise ApiBadRequest(LDAP_ERR_MESSAGES["default"]) user_maps = await get_user_map_by_next_id(user["next_id"]) # Locating auth source. Prioritizes external syncs next_auth = None for user_map in user_maps: result = None if user_map["provider_id"] == env("LDAP_DC") and env.int("ENABLE_LDAP_SYNC"): result = auth_via_ldap(user_map, password, env) elif user_map["provider_id"] == env("TENANT_ID") and env.int( "ENABLE_AZURE_SYNC" ): auth_via_azure(user_map) elif user_map["provider_id"] == "NEXT-created": next_auth = user_map if result: auth_entry = { "next_id": user_map["next_id"], "username": user["username"], "email": user["email"], "encrypted_private_key": user_map["encrypted_key"], "public_key": user_map["public_key"], } await create_auth_entry(auth_entry) return result # Authorization via NEXT if next_auth and env.int("ENABLE_NEXT_BASE_USE"): return await auth_via_next(next_auth, password, env) raise ApiBadRequest("Invalid authentication source.")