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 delete_pack(request, pack_id): """Delete pack from NEXT Args: request: object: request object pack_id: str: ID of pack to delete """ txn_key, txn_user_id = await utils.get_transactor_key(request) pack = await packs_query.get_pack_by_pack_id(request.app.config.DB_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( request.app.config.DB_CONN, pack_id ) if txn_user_id not in owners and not await utils.check_admin_status(txn_user_id): raise ApiBadRequest( "Error: You do not have the authorization to delete this pack." ) await packs_query.delete_pack_by_id(request.app.config.DB_CONN, pack_id) return json( { "message": "Pack {} successfully deleted".format(pack_id), "deleted": 1, "id": pack_id, } )
async def update_proposal(request, proposal_id): """Update proposal.""" 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 ("REJECTED", "APPROVED"): raise ApiBadRequest( "Bad Request: status must be either 'REJECTED' or 'APPROVED'") txn_key, txn_user_id = await utils.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) if txn_user_id not in approvers_list["approvers"]: raise ApiBadRequest( "Bad Request: You don't have the authorization to APPROVE or REJECT the proposal" ) conn.close() 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 utils.send(request.app.config.VAL_CONN, batch_list, request.app.config.TIMEOUT) return json({"proposal_id": proposal_id})
def validate_fields(required_fields, body): try: for field in required_fields: if body.get(field) is None: raise ApiBadRequest( "Bad Request: {} field is required".format(field)) except ValueError: raise ApiBadRequest("Bad Request: Improper JSON format")
def validate_fields(required_fields, body): """Checks that all required_fields are in body, raises exception if not.""" try: for field in required_fields: if body.get(field) is None: raise ApiBadRequest( "Bad Request: {} field is required".format(field)) except ValueError: raise ApiBadRequest("Bad Request: Improper JSON format")
async def send(conn, batch_list, timeout, webhook=False): """Send batch_list to sawtooth.""" batch_request = client_batch_submit_pb2.ClientBatchSubmitRequest() batch_request.batches.extend(list(batch_list.batches)) validator_response = await conn.send( validator_pb2.Message.CLIENT_BATCH_SUBMIT_REQUEST, batch_request.SerializeToString(), timeout, ) client_response = client_batch_submit_pb2.ClientBatchSubmitResponse() client_response.ParseFromString(validator_response.content) status = client_response.status if not webhook: if status == client_batch_submit_pb2.ClientBatchSubmitResponse.INTERNAL_ERROR: raise ApiInternalError("Internal Error") elif status == client_batch_submit_pb2.ClientBatchSubmitResponse.INVALID_BATCH: raise ApiBadRequest("Invalid Batch") elif status == client_batch_submit_pb2.ClientBatchSubmitResponse.QUEUE_FULL: raise ApiInternalError("Queue Full") elif status != client_batch_submit_pb2.ClientBatchSubmitResponse.OK: return None status_request = client_batch_submit_pb2.ClientBatchStatusRequest() status_request.batch_ids.extend( list(b.header_signature for b in batch_list.batches)) status_request.wait = True status_request.timeout = timeout validator_response = await conn.send( validator_pb2.Message.CLIENT_BATCH_STATUS_REQUEST, status_request.SerializeToString(), timeout, ) status_response = client_batch_submit_pb2.ClientBatchStatusResponse() status_response.ParseFromString(validator_response.content) status = status_response.status if not webhook: if status != client_batch_submit_pb2.ClientBatchStatusResponse.OK: raise ApiInternalError("Internal Error") elif status != client_batch_submit_pb2.ClientBatchStatusResponse.OK: return None response = status_response.batch_statuses[0] status = response.status if not webhook: if status == client_batch_submit_pb2.ClientBatchStatus.INVALID: raise ApiBadRequest("Bad Request: {}".format( response.invalid_transactions[0].message)) elif status == client_batch_submit_pb2.ClientBatchStatus.PENDING: raise ApiInternalError("Internal Error: Transaction timed out.") elif status == client_batch_submit_pb2.ClientBatchStatus.UNKNOWN: raise ApiInternalError("Internal Error: Unspecified error.") return status
async def send(conn, batch_list, timeout): batch_request = client_batch_submit_pb2.ClientBatchSubmitRequest() batch_request.batches.extend(list(batch_list.batches)) validator_response = await conn.send( validator_pb2.Message.CLIENT_BATCH_SUBMIT_REQUEST, batch_request.SerializeToString(), timeout, ) client_response = client_batch_submit_pb2.ClientBatchSubmitResponse() client_response.ParseFromString(validator_response.content) if (client_response.status == client_batch_submit_pb2.ClientBatchSubmitResponse.INTERNAL_ERROR): raise ApiInternalError("Internal Error") elif (client_response.status == client_batch_submit_pb2.ClientBatchSubmitResponse.INVALID_BATCH): raise ApiBadRequest("Invalid Batch") elif (client_response.status == client_batch_submit_pb2.ClientBatchSubmitResponse.QUEUE_FULL): raise ApiInternalError("Queue Full") status_request = client_batch_submit_pb2.ClientBatchStatusRequest() status_request.batch_ids.extend( list(b.header_signature for b in batch_list.batches)) status_request.wait = True status_request.timeout = timeout validator_response = await conn.send( validator_pb2.Message.CLIENT_BATCH_STATUS_REQUEST, status_request.SerializeToString(), timeout, ) status_response = client_batch_submit_pb2.ClientBatchStatusResponse() status_response.ParseFromString(validator_response.content) if status_response.status != client_batch_submit_pb2.ClientBatchStatusResponse.OK: raise ApiInternalError("Internal Error") resp = status_response.batch_statuses[0] if resp.status == client_batch_submit_pb2.ClientBatchStatus.COMMITTED: return resp elif resp.status == client_batch_submit_pb2.ClientBatchStatus.INVALID: raise ApiBadRequest("Bad Request: {}".format( resp.invalid_transactions[0].message)) elif resp.status == client_batch_submit_pb2.ClientBatchStatus.PENDING: raise ApiInternalError( "Internal Error: Transaction submitted but timed out.") elif resp.status == client_batch_submit_pb2.ClientBatchStatus.UNKNOWN: raise ApiInternalError( "Internal Error: Something went wrong. Try again later.")
def auth_via_ldap(user_map, password, env): """Authorize via LDAP credentials to access NEXT.""" ldap_server = env("LDAP_SERVER") if ldap_server: server = Server(ldap_server) conn = Connection( server, user=user_map["remote_id"], password=password, read_only=True ) if not conn.bind(): ldap_login_msg = re.search( "data ([0-9a-fA-F]*), v[0-9a-fA-F]*", conn.result["message"] ) if ldap_login_msg and ldap_login_msg.group(1): ldap_err_code = ldap_login_msg.group(1) login_error = LDAP_ERR_MESSAGES.get( ldap_err_code, LDAP_ERR_MESSAGES["default"] ) else: login_error = LDAP_ERR_MESSAGES["default"] raise ApiUnauthorized(login_error) conn.unbind() token = generate_api_key(env("SECRET_KEY"), user_map["next_id"]) return utils.create_authorization_response( token, {"message": "Authorization successful", "next_id": user_map["next_id"]}, ) raise ApiBadRequest("Missing LDAP_SERVER env variable.")
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 get_block(request, block_id): if "?head=" in request.url: raise ApiBadRequest( "Bad Request: 'head' parameter should not be specified") block_resource = await blocks_query.fetch_block_by_id( request.app.config.DB_CONN, block_id) return json({"data": block_resource, "link": request.url})
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_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_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 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 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 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_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 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 get_latest_block(request): if "?head=" in request.url: raise ApiBadRequest( "Bad Request: 'head' parameter should not be specified") block_resource = await blocks_query.fetch_latest_block_with_retry( request.app.config.DB_CONN) url = request.url.replace("latest", block_resource.get("id")) return json({"data": block_resource, "link": url})
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.")
async def get_block(request, block_id): """Get a specific block, by block_id""" 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_block_by_id(conn, block_id) conn.close() return json({"data": block_resource, "link": request.url})
async def get_latest_block(request): """Get the newest block on blockchain.""" 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 get_block(request, block_id): if "?head=" in request.url: raise ApiBadRequest( "Bad Request: 'head' parameter should not be specified") conn = await db_utils.create_connection( request.app.config.DB_HOST, request.app.config.DB_PORT, request.app.config.DB_NAME, ) block_resource = await blocks_query.fetch_block_by_id(conn, block_id) conn.close() return json({"data": block_resource, "link": request.url})
async def check_admin_status(next_id): """Verfiy that a user is a member of NEXT admins. Return boolean. Args: next_id: str: user's next_id """ conn = await create_connection() admin_role = await get_role_by_name(conn, "NextAdmins") if not admin_role: raise ApiBadRequest("NEXT administrator group has not been created.") admin_membership = await get_role_membership(conn, next_id, admin_role[0]["role_id"]) conn.close() if admin_membership: return True return False
async def get_latest_block(request): if "?head=" in request.url: raise ApiBadRequest( "Bad Request: 'head' parameter should not be specified") conn = await db_utils.create_connection( request.app.config.DB_HOST, request.app.config.DB_PORT, request.app.config.DB_NAME, ) 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_pack(request): """Create a new pack""" required_fields = ["owners", "name", "roles"] utils.validate_fields(required_fields, request.json) pack_title = " ".join(request.json.get("name").split()) response = await packs_query.packs_search_duplicate( request.app.config.DB_CONN, pack_title) if not response: pack_id = str(uuid4()) await packs_query.create_pack_resource( request.app.config.DB_CONN, pack_id, request.json.get("owners"), pack_title, request.json.get("description"), ) await packs_query.add_roles(request.app.config.DB_CONN, pack_id, request.json.get("roles")) return create_pack_response(request, pack_id) raise ApiBadRequest( "Error: Could not create this pack because the pack name already exists." )
async def update_manager(request, next_id): """Update a user's manager.""" 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()) if await utils.check_admin_status(txn_user_id): 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=[request.json.get("id")], ) await utils.send(request.app.config.VAL_CONN, batch_list, request.app.config.TIMEOUT) else: raise ApiBadRequest("Proposal opener is not an Next Admin.") return json({"proposal_id": proposal_id})
async def update_user_details(request): """Update the details associated with a user. This is NEXT admin only capability. Args: request: obj: request object from inbound request """ log_request(request) # Checks for action viability env = Env() if not env.int("ENABLE_NEXT_BASE_USE", 0): raise ApiDisabled("This action is not enabled in this mode.") required_fields = ["next_id", "name", "username", "email"] 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 ApiForbidden("You are not a NEXT Administrator.") conn = await create_connection() user = await users_query.users_search_duplicate( conn, request.json.get("username")) if user and user[0]["next_id"] != request.json.get("next_id"): conn.close() raise ApiBadRequest( "Username already exists. Please give a different Username.") # Get resources for update user_info = await users_query.fetch_user_resource( conn, request.json.get("next_id")) if "manager_id" in user_info: manager = user_info["manager_id"] else: manager = "" conn.close() 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" # Build and submit transaction batch_list = User().update.batch_list( signer_keypair=txn_key, signer_user_id=txn_user_id, next_id=request.json.get("next_id"), name=request.json.get("name"), username=request.json.get("username"), email=request.json.get("email"), metadata=set_metadata, manager_id=manager, ) await send(request.app.config.VAL_CONN, batch_list, request.app.config.TIMEOUT) # Update_auth_table auth_updates = { "username": request.json.get("username"), "email": request.json.get("email"), } await auth_query.update_auth(request.json.get("next_id"), auth_updates) # Send back success response return json({"message": "User information was successfully updated."})
async def create_new_user(request): """Create a new user.""" required_fields = ["name", "username", "password", "email"] utils.validate_fields(required_fields, request.json) username_created = request.json.get("username") conn = await db_utils.create_connection( request.app.config.DB_HOST, request.app.config.DB_PORT, request.app.config.DB_NAME, ) # Check if username already exists if await users_query.fetch_username_match_count(conn, username_created) > 0: # Throw Error response to Next_UI raise ApiBadRequest( "Username already exists. Please give a different Username.") conn.close() # Generate keys key_pair = Key() next_id = str(uuid4()) encrypted_private_key = encrypt_private_key(AES_KEY, key_pair.public_key, key_pair.private_key_bytes) # Build create user transaction batch_list = User().batch_list( signer_keypair=key_pair, signer_user_id=next_id, next_id=next_id, name=request.json.get("name"), username=request.json.get("username"), email=request.json.get("email"), metadata=request.json.get("metadata"), manager=request.json.get("manager"), key=key_pair.public_key, ) # Submit transaction and wait for complete await utils.send(request.app.config.VAL_CONN, batch_list, request.app.config.TIMEOUT) # Save new user in auth table hashed_password = hashlib.sha256( request.json.get("password").encode("utf-8")).hexdigest() auth_entry = { "next_id": next_id, "hashed_password": hashed_password, "encrypted_private_key": encrypted_private_key, "username": request.json.get("username"), "email": request.json.get("email"), } mapping_data = { "next_id": next_id, "provider_id": None, "remote_id": None, "public_key": key_pair.public_key, "encrypted_key": encrypted_private_key, "active": True, } # Insert to user_mapping and close conn = await db_utils.create_connection( request.app.config.DB_HOST, request.app.config.DB_PORT, request.app.config.DB_NAME, ) await auth_query.create_auth_entry(conn, auth_entry) await users_query.create_user_map_entry(conn, mapping_data) conn.close() # Send back success response return create_user_response(request, next_id)
async def authorize(request): """ API Endpoint to authenticate and login to the NEXT platform. """ required_fields = ["id", "password", "auth_source"] utils.validate_fields(required_fields, request.json) username = request.json.get("id") password = request.json.get("password") auth_source = request.json.get("auth_source") if auth_source == "next": 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("No password or email is set on this account.") # TODO: send email confirmation with password set link raise ApiUnauthorized("No password is set on this account.") if auth_info.get("hashed_password") != hashed_pwd: # TODO: rate limit password attempts raise ApiUnauthorized("The password you entered is incorrect.") 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"), }, ) if auth_source == "ldap": if LDAP_SERVER: if username != "" and password != "": ldap_user_dn = await auth_query.fetch_dn_by_username(request) server = Server(LDAP_SERVER) conn = Connection( server, user=ldap_user_dn, password=password, read_only=True ) if not conn.bind(): ldap_login_msg = re.search( "data ([0-9a-fA-F]*), v[0-9a-fA-F]*", conn.result["message"] ) if ldap_login_msg and ldap_login_msg.group(1): ldap_err_code = ldap_login_msg.group(1) login_error = LDAP_ERR_MESSAGES.get( ldap_err_code, LDAP_ERR_MESSAGES["default"] ) else: login_error = LDAP_ERR_MESSAGES["default"] raise ApiUnauthorized(login_error) auth_info = await auth_query.fetch_info_by_username(request) conn.unbind() 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"), }, ) raise ApiBadRequest(LDAP_ERR_MESSAGES["default"]) raise ApiBadRequest("Missing LDAP_SERVER env variable.") raise ApiBadRequest("Invalid authentication source.")